Merge "Fix the script to run perf test"
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..b061ccf
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Ember Rose <emberr@google.com> <ashleyrose@google.com>
diff --git a/Android.bp b/Android.bp
index ff17260..6fc233c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -162,49 +162,62 @@
     path: "wifi/java",
 }
 
-framework_srcs = [
-    // Java/AIDL sources under frameworks/base
-    ":framework-core-sources",
-    ":framework-drm-sources",
-    ":framework-graphics-sources",
-    ":framework-keystore-sources",
-    ":framework-location-sources",
-    ":framework-lowpan-sources",
-    ":framework-media-sources",
-    ":framework-mca-effect-sources",
-    ":framework-mca-filterfw-sources",
-    ":framework-mca-filterpacks-sources",
-    ":framework-opengl-sources",
-    ":framework-rs-sources",
-    ":framework-sax-sources",
-    ":framework-telecomm-sources",
-    ":framework-telephony-sources",
-    ":framework-wifi-sources",
-    ":PacProcessor-aidl-sources",
-    ":ProxyHandler-aidl-sources",
+filegroup {
+    name: "framework-non-updatable-sources",
+    srcs: [
+        // Java/AIDL sources under frameworks/base
+        ":framework-core-sources",
+        ":framework-drm-sources",
+        ":framework-graphics-sources",
+        ":framework-keystore-sources",
+        ":framework-location-sources",
+        ":framework-lowpan-sources",
+        ":framework-media-sources",
+        ":framework-mca-effect-sources",
+        ":framework-mca-filterfw-sources",
+        ":framework-mca-filterpacks-sources",
+        ":framework-opengl-sources",
+        ":framework-rs-sources",
+        ":framework-sax-sources",
+        ":framework-telecomm-sources",
+        ":framework-telephony-sources",
+        ":framework-wifi-sources",
+        ":PacProcessor-aidl-sources",
+        ":ProxyHandler-aidl-sources",
 
-    // AIDL sources from external directories
-    ":dumpstate_aidl",
-    ":framework_native_aidl",
-    ":gatekeeper_aidl",
-    ":gsiservice_aidl",
-    ":incidentcompanion_aidl",
-    ":installd_aidl",
-    ":keystore_aidl",
-    ":libaudioclient_aidl",
-    ":libbinder_aidl",
-    ":libbluetooth-binder-aidl",
-    ":libcamera_client_aidl",
-    ":libcamera_client_framework_aidl",
-    ":libupdate_engine_aidl",
-    ":storaged_aidl",
-    ":vold_aidl",
+        // AIDL sources from external directories
+        ":dumpstate_aidl",
+        ":framework_native_aidl",
+        ":gatekeeper_aidl",
+        ":gsiservice_aidl",
+        ":incidentcompanion_aidl",
+        ":installd_aidl",
+        ":keystore_aidl",
+        ":libaudioclient_aidl",
+        ":libbinder_aidl",
+        ":libbluetooth-binder-aidl",
+        ":libcamera_client_aidl",
+        ":libcamera_client_framework_aidl",
+        ":libupdate_engine_aidl",
+        ":storaged_aidl",
+        ":vold_aidl",
 
-    // etc.
-    "core/java/**/*.logtags",
-    ":framework-javastream-protos",
-    ":framework-statslog-gen",
-]
+        // For the generated R.java and Manifest.java
+        ":framework-res{.aapt.srcjar}",
+
+        // etc.
+        ":framework-javastream-protos",
+        ":framework-statslog-gen",
+    ],
+}
+
+filegroup {
+    name: "framework-all-sources",
+    srcs: [
+        ":framework-non-updatable-sources",
+        ":updatable-media-srcs",
+    ],
+}
 
 java_defaults {
     name: "framework-aidl-export-defaults",
@@ -229,34 +242,19 @@
             "wifi/java",
         ],
     },
+
+    required: [
+        // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
+        "gps_debug.conf",
+	"protolog.conf.json.gz",
+    ],
 }
 
-java_defaults {
-    name: "framework-defaults",
-    defaults: ["framework-aidl-export-defaults"],
-    installable: true,
-
-    srcs: framework_srcs,
-
-    aidl: {
-        generate_get_transaction_name: true,
-    },
-
-    exclude_srcs: [
-        // See comment on framework-atb-backward-compatibility module below
-        "core/java/android/content/pm/AndroidTestBaseUpdater.java",
-    ],
-
-    sdk_version: "core_platform",
-    libs: [
-        "ext",
-        "updatable_media_stubs",
-    ],
-
-    jarjar_rules: ":framework-jarjar-rules",
-
+// Collection of classes that are generated from non-Java files that are not listed in
+// framework_srcs. These have no or very limited dependency to the framework.
+java_library {
+    name: "framework-internal-utils",
     static_libs: [
-        "mimemap",
         "apex_aidl_interface-java",
         "suspend_control_aidl_interface-java",
         "framework-protos",
@@ -290,12 +288,36 @@
         "com.android.sysprop.apex",
         "PlatformProperties",
     ],
+    sdk_version: "core_platform",
+    installable: false,
+}
 
-    required: [
-        // TODO: remove gps_debug when the build system propagates "required" properly.
-        "gps_debug.conf",
+java_defaults {
+    name: "framework-defaults",
+    defaults: ["framework-aidl-export-defaults"],
+    installable: true,
+
+    aidl: {
+        generate_get_transaction_name: true,
+    },
+
+    srcs: ["core/java/**/*.logtags"],
+
+    exclude_srcs: [
+        // See comment on framework-atb-backward-compatibility module below
+        "core/java/android/content/pm/AndroidTestBaseUpdater.java",
     ],
 
+    sdk_version: "core_platform",
+    libs: [
+        "ext",
+        "updatable_media_stubs",
+    ],
+
+    jarjar_rules: ":framework-jarjar-rules",
+
+    static_libs: ["framework-internal-utils"],
+
     dxflags: [
         "--core-library",
         "--multi-dex",
@@ -343,6 +365,7 @@
 java_library {
     name: "framework-minus-apex",
     defaults: ["framework-defaults"],
+    srcs: [":framework-non-updatable-sources"],
     javac_shard_size: 150,
 }
 
@@ -363,8 +386,16 @@
 }
 
 java_library {
+    name: "framework-all",
+    defaults: ["framework-defaults"],
+    srcs: [":framework-all-sources"],
+    installable: false,
+}
+
+java_library {
     name: "framework-annotation-proc",
     defaults: ["framework-defaults"],
+    srcs: [":framework-all-sources"],
     installable: false,
     plugins: [
         "unsupportedappusage-annotation-processor",
@@ -374,7 +405,6 @@
 
 platform_compat_config {
     name: "framework-platform-compat-config",
-    prefix: "framework",
     src: ":framework-annotation-proc",
 }
 
@@ -487,7 +517,6 @@
     sdk_version: "core_platform",
     static_libs: [
         "libphonenumber-platform",
-        "nist-sip",
         "tagsoup",
         "rappor",
         "libtextclassifier-java",
@@ -760,7 +789,7 @@
     ],
 }
 
-// TODO: Don't rely on this list once droiddoc can take a list of packages to document
+// TODO: Don't rely on this list by switching package.html into package-info.java
 frameworks_base_subdirs = [
     "core/java",
     "graphics/java",
@@ -780,13 +809,6 @@
     "rs/java",
 ]
 
-packages_to_document = [
-    "android",
-    "javax/microedition/khronos",
-    "org/apache/http/conn",
-    "org/apache/http/params",
-]
-
 // Make the api/current.txt file available for use by modules in other
 // directories.
 filegroup {
@@ -876,20 +898,33 @@
     // replacement (with $1, $2 backreferences to the regex groups)
     "'$$1https://docs.oracle.com/javase/8/docs/$$2\">' "
 
+packages_to_document = [
+    "android",
+    "dalvik",
+    "java",
+    "javax",
+    "junit",
+    "org.apache.http",
+    "org.json",
+    "org.w3c.dom",
+    "org.xml.sax",
+    "org.xmlpull",
+]
+
 stubs_defaults {
     name: "framework-doc-stubs-default",
     srcs: [
+        ":framework-non-updatable-sources",
+        "core/java/**/*.logtags",
         "test-base/src/**/*.java",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
         ":core_public_api_files",
-        ":updatable-media-srcs-without-aidls",
+        ":updatable-media-srcs",
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
         ":jobscheduler-framework-source",
     ],
-    srcs_lib: "framework-minus-apex",
-    srcs_lib_whitelist_pkgs: packages_to_document,
     libs: framework_docs_only_libs,
     local_sourcepaths: frameworks_base_subdirs,
     create_doc_stubs: true,
@@ -899,7 +934,7 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
@@ -944,18 +979,19 @@
 stubs_defaults {
     name: "metalava-api-stubs-default",
     srcs: [
+        ":framework-non-updatable-sources",
+        "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
         ":core_public_api_files",
-        ":updatable-media-srcs-without-aidls",
+        ":updatable-media-srcs",
         ":jobscheduler-framework-source",
     ],
-    srcs_lib: "framework-minus-apex",
-    srcs_lib_whitelist_pkgs: packages_to_document,
+    libs: ["framework-internal-utils"],
     local_sourcepaths: frameworks_base_subdirs,
     installable: false,
     annotations_enabled: true,
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
@@ -965,6 +1001,8 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
+    sdk_version: "core_platform",
+    filter_packages: packages_to_document,
 }
 
 droidstubs {
@@ -1289,7 +1327,7 @@
     installable: false,
     sdk_version: "core_platform",
     annotations_enabled: true,
-    previous_api: ":last-released-public-api",
+    previous_api: ":last-released-public-api-for-metalava-annotations",
     merge_annotations_dirs: [
         "metalava-manual",
         "ojluni-annotated-sdk-stubs",
@@ -1328,6 +1366,7 @@
         ":openjdk_java_files",
         ":opt-telephony-common-srcs",
     ],
+
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -1462,7 +1501,7 @@
 // annotations to private apis
 aidl_mapping {
     name: "framework-aidl-mappings",
-    srcs: framework_srcs,
+    srcs: [":framework-all-sources"],
     output: "framework-aidl-mappings.txt",
 }
 
@@ -1475,3 +1514,25 @@
         targets: ["droidcore"],
     },
 }
+
+// Avoid including Parcelable classes as we don't want to have two copies of
+// Parcelable cross the process.
+filegroup {
+    name: "framework-telephony-stack-shared-srcs",
+    srcs: [
+        "core/java/android/os/RegistrantList.java",
+        "core/java/android/os/Registrant.java",
+        "core/java/android/util/LocalLog.java",
+        "core/java/android/util/Slog.java",
+        "core/java/android/util/TimeUtils.java",
+        "core/java/com/android/internal/os/SomeArgs.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+        "core/java/com/android/internal/util/State.java",
+        "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/XmlUtils.java",
+        "core/java/com/android/internal/util/HexDump.java",
+        "core/java/com/android/internal/util/IndentingPrintWriter.java",
+        "core/java/com/android/internal/util/DumpUtils.java"
+    ],
+}
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f311fc8..6a909c0 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -253,6 +253,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/DynamicAndroidInstallationService)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/DefaultContainerService)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/CaptivePortalLogin)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 2b6f455..9cfc3d2 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -188,8 +188,8 @@
 
             @Override
             public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                    Rect minimizedHomeBounds) throws RemoteException {
+                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                    Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
                 final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
                 final boolean moveRecentsToTop = finishCase.second;
                 makeInterval();
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 4b7a7f6..32107b4 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -119,9 +119,20 @@
     }
 
     @Test
+    public void createUser() {
+        while (mRunner.keepRunning()) {
+            final int userId = createUserNoFlags();
+
+            mRunner.pauseTiming();
+            removeUser(userId);
+            mRunner.resumeTiming();
+        }
+    }
+
+    @Test
     public void createAndStartUser() throws Exception {
         while (mRunner.keepRunning()) {
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
 
             final CountDownLatch latch = new CountDownLatch(1);
             registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
@@ -140,7 +151,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             mRunner.resumeTiming();
 
             switchUser(userId);
@@ -197,7 +208,7 @@
     public void stopUser() throws Exception {
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             final CountDownLatch latch = new CountDownLatch(1);
             registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
             mIam.startUserInBackground(userId);
@@ -217,7 +228,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             final CountDownLatch latch = new CountDownLatch(1);
             registerUserSwitchObserver(null, latch, userId);
             mRunner.resumeTiming();
@@ -237,7 +248,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
+            final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
             switchUser(userId);
             final CountDownLatch latch = new CountDownLatch(1);
             InstrumentationRegistry.getContext().registerReceiver(new BroadcastReceiver() {
@@ -396,7 +407,6 @@
     public void managedProfileCreateUnlockInstallAndLaunchApp() throws Exception {
         assumeTrue(mHasManagedUserFeature);
 
-        final String packageName = "perftests.multiuser.apps.dummyapp";
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
@@ -433,12 +443,12 @@
     }
 
     /** Creates a new user, returning its userId. */
-    private int createUser() {
-        return createUser(0);
+    private int createUserNoFlags() {
+        return createUserWithFlags(/* flags= */ 0);
     }
 
     /** Creates a new user with the given flags, returning its userId. */
-    private int createUser(int flags) {
+    private int createUserWithFlags(int flags) {
         int userId = mUm.createUser("TestUser", flags).id;
         mUsersToRemove.add(userId);
         return userId;
@@ -501,7 +511,7 @@
     private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception {
         final int origUser = mAm.getCurrentUser();
         // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
-        final int testUser = createUser();
+        final int testUser = createUserNoFlags();
         final CountDownLatch latch1 = new CountDownLatch(1);
         registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
         mAm.switchUser(testUser);
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index aa255bf..9d5becb 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.os.IMaintenanceActivityListener;
 import android.os.UserHandle;
 
 /** @hide */
@@ -44,8 +43,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
     void exitIdle(String reason);
-    boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
-    void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
     int setPreIdleTimeoutMode(int Mode);
     void resetPreIdleTimeoutMode();
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 62930b0..65aaf20 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -51,14 +51,12 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
-import android.os.IMaintenanceActivityListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -69,6 +67,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.KeyValueListParser;
 import android.util.MutableLong;
 import android.util.Pair;
@@ -81,7 +80,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
@@ -448,7 +446,6 @@
                                                        // down.
     private boolean mJobsActive;
     private boolean mAlarmsActive;
-    private boolean mReportedMaintenanceActivity;
 
     /* Factor to apply to INACTIVE_TIMEOUT and IDLE_AFTER_INACTIVE_TIMEOUT in order to enter
      * STATE_IDLE faster or slower. Don't apply this to SENSING_TIMEOUT or LOCATING_TIMEOUT because:
@@ -463,9 +460,6 @@
 
     public final AtomicFile mConfigFile;
 
-    private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
-            new RemoteCallbackList<IMaintenanceActivityListener>();
-
     /**
      * Package names the system has white-listed to opt out of power save restrictions,
      * except for device idle mode.
@@ -1309,7 +1303,6 @@
     private static final int MSG_REPORT_IDLE_OFF = 4;
     private static final int MSG_REPORT_ACTIVE = 5;
     private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
-    private static final int MSG_REPORT_MAINTENANCE_ACTIVITY = 7;
     private static final int MSG_FINISH_IDLE_OP = 8;
     private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
     private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
@@ -1410,22 +1403,6 @@
                     int appId = msg.arg1;
                     checkTempAppWhitelistTimeout(appId);
                 } break;
-                case MSG_REPORT_MAINTENANCE_ACTIVITY: {
-                    // TODO: What is keeping the device awake at this point? Does it need to be?
-                    boolean active = (msg.arg1 == 1);
-                    final int size = mMaintenanceActivityListeners.beginBroadcast();
-                    try {
-                        for (int i = 0; i < size; i++) {
-                            try {
-                                mMaintenanceActivityListeners.getBroadcastItem(i)
-                                        .onMaintenanceActivityChanged(active);
-                            } catch (RemoteException ignored) {
-                            }
-                        }
-                    } finally {
-                        mMaintenanceActivityListeners.finishBroadcast();
-                    }
-                } break;
                 case MSG_FINISH_IDLE_OP: {
                     // mActiveIdleWakeLock is held at this point
                     decActiveIdleOps();
@@ -1594,16 +1571,6 @@
             }
         }
 
-        @Override public boolean registerMaintenanceActivityListener(
-                IMaintenanceActivityListener listener) {
-            return DeviceIdleController.this.registerMaintenanceActivityListener(listener);
-        }
-
-        @Override public void unregisterMaintenanceActivityListener(
-                IMaintenanceActivityListener listener) {
-            DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
-        }
-
         @Override public int setPreIdleTimeoutMode(int mode) {
             getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
                     null);
@@ -3131,7 +3098,6 @@
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
-            reportMaintenanceActivityIfNeededLocked();
             if (!active) {
                 exitMaintenanceEarlyIfNeededLocked();
             }
@@ -3147,19 +3113,6 @@
         }
     }
 
-    boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener) {
-        synchronized (this) {
-            mMaintenanceActivityListeners.register(listener);
-            return mReportedMaintenanceActivity;
-        }
-    }
-
-    void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener) {
-        synchronized (this) {
-            mMaintenanceActivityListeners.unregister(listener);
-        }
-    }
-
     @VisibleForTesting
     int setPreIdleTimeoutMode(int mode) {
         return setPreIdleTimeoutFactor(getPreIdleTimeoutByMode(mode));
@@ -3281,17 +3234,6 @@
         }
     }
 
-    void reportMaintenanceActivityIfNeededLocked() {
-        boolean active = mJobsActive;
-        if (active == mReportedMaintenanceActivity) {
-            return;
-        }
-        mReportedMaintenanceActivity = active;
-        Message msg = mHandler.obtainMessage(MSG_REPORT_MAINTENANCE_ACTIVITY,
-                mReportedMaintenanceActivity ? 1 : 0, 0);
-        mHandler.sendMessage(msg);
-    }
-
     @VisibleForTesting
     long getNextAlarmTime() {
         return mNextAlarmTime;
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 a633350..329d4b7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -56,8 +56,6 @@
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
@@ -66,7 +64,6 @@
 import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.SystemClock;
-import android.os.Temperature;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.os.WorkSource;
@@ -81,7 +78,6 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.ArrayUtils;
@@ -105,6 +101,8 @@
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.StorageController;
 import com.android.server.job.controllers.TimeController;
+import com.android.server.job.restrictions.JobRestriction;
+import com.android.server.job.restrictions.ThermalStatusRestriction;
 
 import libcore.util.EmptyArray;
 
@@ -186,12 +184,12 @@
     private final DeviceIdleJobsController mDeviceIdleJobsController;
     /** Needed to get remaining quota time. */
     private final QuotaController mQuotaController;
-
-    /** Need directly for receiving thermal events */
-    private IThermalService mThermalService;
-    /** Thermal constraint. */
-    @GuardedBy("mLock")
-    private boolean mThermalConstraint = false;
+    /**
+     * List of restrictions.
+     * Note: do not add to or remove from this list at runtime except in the constructor, because we
+     * do not synchronize access to this list.
+     */
+    private final List<JobRestriction> mJobRestrictions;
 
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -285,19 +283,6 @@
         }
     }
 
-    /**
-     *  Thermal event received from Thermal Service
-     */
-    private final class ThermalStatusListener extends IThermalStatusListener.Stub {
-        @Override public void onStatusChange(int status) {
-            // Throttle for Temperature.THROTTLING_SEVERE and above
-            synchronized (mLock) {
-                mThermalConstraint = status >= Temperature.THROTTLING_SEVERE;
-            }
-            onControllerStateChanged();
-        }
-    }
-
     static class MaxJobCounts {
         private final KeyValueListParser.IntValue mTotal;
         private final KeyValueListParser.IntValue mMaxBg;
@@ -1292,6 +1277,10 @@
         mQuotaController = new QuotaController(this);
         mControllers.add(mQuotaController);
 
+        // Create restrictions
+        mJobRestrictions = new ArrayList<>();
+        mJobRestrictions.add(new ThermalStatusRestriction(this));
+
         // If the job store determined that it can't yet reschedule persisted jobs,
         // we need to start watching the clock.
         if (!mJobs.jobTimesInflatedValid()) {
@@ -1383,15 +1372,9 @@
 
             // Remove any jobs that are not associated with any of the current users.
             cancelJobsForNonExistentUsers();
-            // Register thermal callback
-            mThermalService = IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-            if (mThermalService != null) {
-                try {
-                    mThermalService.registerThermalStatusListener(new ThermalStatusListener());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to register thermal callback.", e);
-                }
+
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                mJobRestrictions.get(i).onSystemServicesReady();
             }
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
@@ -1833,9 +1816,28 @@
         }
     }
 
-    private boolean isJobThermalConstrainedLocked(JobStatus job) {
-        return mThermalConstraint && job.hasConnectivityConstraint()
-                && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP);
+    /**
+     * Check if a job is restricted by any of the declared {@link JobRestriction}s.
+     * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not
+     * be restricted, thus we won't even perform the check, but simply return null early.
+     *
+     * @param job to be checked
+     * @return the first {@link JobRestriction} restricting the given job that has been found; null
+     * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP}
+     * or higher.
+     */
+    private JobRestriction checkIfRestricted(JobStatus job) {
+        if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) {
+            // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted
+            return null;
+        }
+        for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+            final JobRestriction restriction = mJobRestrictions.get(i);
+            if (restriction.isJobRestricted(job)) {
+                return restriction;
+            }
+        }
+        return null;
     }
 
     private void stopNonReadyActiveJobsLocked() {
@@ -1849,10 +1851,13 @@
                 serviceContext.cancelExecutingJobLocked(
                         JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
                         "cancelled due to unsatisfied constraints");
-            } else if (isJobThermalConstrainedLocked(running)) {
-                serviceContext.cancelExecutingJobLocked(
-                        JobParameters.REASON_DEVICE_THERMAL,
-                        "cancelled due to thermal condition");
+            } else {
+                final JobRestriction restriction = checkIfRestricted(running);
+                if (restriction != null) {
+                    final int reason = restriction.getReason();
+                    serviceContext.cancelExecutingJobLocked(reason,
+                            "restricted due to " + JobParameters.getReasonName(reason));
+                }
             }
         }
     }
@@ -2089,7 +2094,7 @@
             return false;
         }
 
-        if (isJobThermalConstrainedLocked(job)) {
+        if (checkIfRestricted(job) != null) {
             return false;
         }
 
@@ -2170,7 +2175,7 @@
             return false;
         }
 
-        if (isJobThermalConstrainedLocked(job)) {
+        if (checkIfRestricted(job) != null) {
             return false;
         }
 
@@ -2982,9 +2987,12 @@
             pw.print("    In parole?: ");
             pw.print(mInParole);
             pw.println();
-            pw.print("    In thermal throttling?: ");
-            pw.print(mThermalConstraint);
-            pw.println();
+
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                pw.print("    ");
+                mJobRestrictions.get(i).dumpConstants(pw);
+                pw.println();
+            }
             pw.println();
 
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
@@ -3005,14 +3013,30 @@
 
                     job.dump(pw, "    ", true, nowElapsed);
 
+
+                    pw.print("    Restricted due to:");
+                    final boolean isRestricted = checkIfRestricted(job) != null;
+                    if (isRestricted) {
+                        for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                            final JobRestriction restriction = mJobRestrictions.get(i);
+                            if (restriction.isJobRestricted(job)) {
+                                final int reason = restriction.getReason();
+                                pw.write(" " + JobParameters.getReasonName(reason) + "[" + reason + "]");
+                            }
+                        }
+                    } else {
+                        pw.print(" none");
+                    }
+                    pw.println(".");
+
                     pw.print("    Ready: ");
                     pw.print(isReadyToBeExecutedLocked(job));
                     pw.print(" (job=");
                     pw.print(job.isReady());
                     pw.print(" user=");
                     pw.print(areUsersStartedLocked(job));
-                    pw.print(" !thermal=");
-                    pw.print(!isJobThermalConstrainedLocked(job));
+                    pw.print(" !restricted=");
+                    pw.print(!isRestricted);
                     pw.print(" !pending=");
                     pw.print(!mPendingJobs.contains(job));
                     pw.print(" !active=");
@@ -3152,7 +3176,9 @@
             proto.end(settingsToken);
 
             proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
-            proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
+            for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
+                mJobRestrictions.get(i).dumpConstants(proto);
+            }
 
             for (int u : mStartedUsers) {
                 proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
@@ -3179,8 +3205,8 @@
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.ARE_USERS_STARTED,
                             areUsersStartedLocked(job));
                     proto.write(
-                            JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_THERMAL_CONSTRAINED,
-                            isJobThermalConstrainedLocked(job));
+                            JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED,
+                            checkIfRestricted(job) != null);
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
                             mPendingJobs.contains(job));
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
@@ -3190,6 +3216,16 @@
                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_USABLE,
                             isComponentUsable(job));
 
+                    for (JobRestriction restriction : mJobRestrictions) {
+                        final long restrictionsToken = proto.start(
+                                JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS);
+                        proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON,
+                                restriction.getReason());
+                        proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING,
+                                restriction.isJobRestricted(job));
+                        proto.end(restrictionsToken);
+                    }
+
                     proto.end(rjToken);
                 }
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 831be0b..400d902 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -2000,10 +2000,10 @@
         private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
                 4 * HOUR_IN_MILLIS;
         private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
-                10 * MINUTE_IN_MILLIS;
+                MINUTE_IN_MILLIS;
         private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
-        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
-                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
+                75; // 75/window = 450/hr = 1/session
         private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
                 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
@@ -2011,7 +2011,7 @@
         private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
                 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
-                20; // 120/hr
+                75; // 450/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
                 10; // 5/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
@@ -2165,7 +2165,7 @@
             mResolver = resolver;
             mResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
-            updateConstants();
+            onChange(true, null);
         }
 
         @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
new file mode 100644
index 0000000..e180c55
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job.restrictions;
+
+import android.app.job.JobInfo;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.controllers.JobStatus;
+
+/**
+ * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
+ * should be scheduled or not based on the state of the system/device.
+ * Every restriction is associated with exactly one reason (from {@link
+ * android.app.job.JobParameters#JOB_STOP_REASON_CODES}), which could be retrieved using {@link
+ * #getReason()}.
+ * Note, that this is not taken into account for the jobs that have priority
+ * {@link JobInfo#PRIORITY_FOREGROUND_APP} or higher.
+ */
+public abstract class JobRestriction {
+
+    final JobSchedulerService mService;
+    private final int mReason;
+
+    JobRestriction(JobSchedulerService service, int reason) {
+        mService = service;
+        mReason = reason;
+    }
+
+    /**
+     * Called when the system boot phase has reached
+     * {@link com.android.server.SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    public void onSystemServicesReady() {
+    }
+
+    /**
+     * Called by {@link JobSchedulerService} to check if it may proceed with scheduling the job (in
+     * case all constraints are satisfied and all other {@link JobRestriction}s are fine with it)
+     *
+     * @param job to be checked
+     * @return false if the {@link JobSchedulerService} should not schedule this job at the moment,
+     * true - otherwise
+     */
+    public abstract boolean isJobRestricted(JobStatus job);
+
+    /** Dump any internal constants the Restriction may have. */
+    public abstract void dumpConstants(IndentingPrintWriter pw);
+
+    /** Dump any internal constants the Restriction may have. */
+    public abstract void dumpConstants(ProtoOutputStream proto);
+
+    /** @return reason code for the Restriction. */
+    public final int getReason() {
+        return mReason;
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
new file mode 100644
index 0000000..b97da59
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job.restrictions;
+
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.IThermalService;
+import android.os.IThermalStatusListener;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.controllers.JobStatus;
+
+public class ThermalStatusRestriction extends JobRestriction {
+    private static final String TAG = "ThermalStatusRestriction";
+
+    private volatile boolean mIsThermalRestricted = false;
+
+    public ThermalStatusRestriction(JobSchedulerService service) {
+        super(service, JobParameters.REASON_DEVICE_THERMAL);
+    }
+
+    @Override
+    public void onSystemServicesReady() {
+        final IThermalService thermalService = IThermalService.Stub.asInterface(
+                ServiceManager.getService(Context.THERMAL_SERVICE));
+        if (thermalService != null) {
+            try {
+                thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
+                    @Override
+                    public void onStatusChange(int status) {
+                        final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
+                        if (mIsThermalRestricted == shouldBeActive) {
+                            return;
+                        }
+                        mIsThermalRestricted = shouldBeActive;
+                        mService.onControllerStateChanged();
+                    }
+                });
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal callback.", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isJobRestricted(JobStatus job) {
+        return mIsThermalRestricted && job.hasConnectivityConstraint();
+    }
+
+    @Override
+    public void dumpConstants(IndentingPrintWriter pw) {
+        pw.print("In thermal throttling?: ");
+        pw.print(mIsThermalRestricted);
+    }
+
+    @Override
+    public void dumpConstants(ProtoOutputStream proto) {
+        proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mIsThermalRestricted);
+    }
+}
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
new file mode 100644
index 0000000..d76a40e
--- /dev/null
+++ b/apex/statsd/Android.bp
@@ -0,0 +1,46 @@
+// 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.
+
+apex {
+    name: "com.android.os.statsd",
+
+    manifest: "apex_manifest.json",
+
+    // optional. if unspecified, a default one is auto-generated
+    //androidManifest: "AndroidManifest.xml",
+
+    // libc.so and libcutils.so are included in the apex
+    // native_shared_libs: ["libc", "libcutils"],
+    // binaries: ["vold"],
+    // java_libs: ["core-all"],
+    // prebuilts: ["my_prebuilt"],
+
+    compile_multilib: "both",
+
+    key: "com.android.os.statsd.key",
+    certificate: ":com.android.os.statsd.certificate",
+}
+
+apex_key {
+    name: "com.android.os.statsd.key",
+    public_key: "com.android.os.statsd.avbpubkey",
+    private_key: "com.android.os.statsd.pem",
+}
+
+android_app_certificate {
+    name: "com.android.os.statsd.certificate",
+    // This will use com.android.os.statsd.x509.pem (the cert) and
+    // com.android.os.statsd.pk8 (the private key)
+    certificate: "com.android.os.statsd",
+}
diff --git a/apex/statsd/apex_manifest.json b/apex/statsd/apex_manifest.json
new file mode 100644
index 0000000..0c0ad86
--- /dev/null
+++ b/apex/statsd/apex_manifest.json
@@ -0,0 +1,5 @@
+{
+  "name": "com.android.os.statsd",
+  "version": 1
+}
+
diff --git a/apex/statsd/com.android.os.statsd.avbpubkey b/apex/statsd/com.android.os.statsd.avbpubkey
new file mode 100644
index 0000000..d78af8b
--- /dev/null
+++ b/apex/statsd/com.android.os.statsd.avbpubkey
Binary files differ
diff --git a/apex/statsd/com.android.os.statsd.pem b/apex/statsd/com.android.os.statsd.pem
new file mode 100644
index 0000000..558e17f
--- /dev/null
+++ b/apex/statsd/com.android.os.statsd.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA893bbpkivKEiNgfknYBSlzC0csaKU/ddBm5Pb4ZFuab+LQSR
+9DDc5JrsmxyrsrvuwL/zAtMbkyYWzEiUxJtx/w0bw8rC90GoPRSCmxyI0ZK8FuPy
+IAQ7UeNfTWZ485mAUaTSasGIfQ3DY4F0P+aUSijeG3NUY02nALHDMqJX7lXR+mL1
+DUYDg05KB0jxQwlYqBeujTPPiAzEqm3PlBoHuan8/qgK2wdQMTVg/fieUD3lupmV
+Wj2dRZgqfBPA16ZbV4Uo0j0bZSf+fQLiXlU2VJGb5i/FQfjLqMKGABDI0MgK7Sc2
+m4ySpV4g4XKDv/vw6Dw4kwWC7mATEVAkH+q6V7uiZeN6a7w30UMtPI8fPaUvAP3L
+VBjCBIv/3m+CKkWcNxOZ3sQBQl5bS05dxcfiVsBvBLYbvQgC+Wy0Sc3b+1pXFT/E
+uAsbZ4CyVsi1+PAdx3h5e2QAyNCXgZDOcvTUyxY6JLTE0LOVHmI4fJEujBex//Oz
+PCRHvC8K+KiljyQWf/NYrLSD3QGYAjVMtQh7yu2yhzWzgBUxyhuv3rY4ATXsN3bJ
+wW4w7/L/RSLSW5+lp/NoJOD9utbsKTyGMHOY6K8JLOmhv3ORoAEmLYlFTI+FqBi9
+AH1HQEKCyh8Z/bYHLUzGWl6FqAMtcnuintv40BbKyt0/D1ItdbSNKmOZ5rkCAwEA
+AQKCAgAY7ll8mRNADYkd1Pi+UVwgMM6B3WJO6z8LZUOhtyxxqmzZ1VnGiShMBrqh
+sPCsuSHTeswxQbvT81TpVZI/91RUKtbn0VbVSFUWyX4AtY4XPtUT0gHy2/vkh0Y6
+93ruDIdd0Wfhmh+GCV4sUhO8ZKpMWpk6XTQHYuzr2UCHcKlkqElrO6qpzLqXNe3D
+iOWBYPc7WBB0RxO0aPnCIq/SCEc55/MBZdSWR80e+sILtNsagPl3djQaoanub3wI
+a0yPv2YfMHHX7H9cfBY8WYsi8bs4MhqqEcAs2m6XtitU3mJpVcooLJYcmOZ1GYZr
+BfYKLouWcnGmNi4IiLHqVzMaQDkEhAZsRaAXCkoVVrFBedLlmLPpiUIQlINF4vxe
+3IcekTKWyMzkU6h+K8T15MU5mLSqeL2Gji1JIwKJno51FZ9uc++pUJVtfYQmNny8
+8RKvQ1hv/S5yLQKgN+VkNbaWlUoMP73dtUe3m/At71/2Dj7xB0KtcgT1lEMrM1GR
+oynJAJLz/d0n5RUUREwkZZMcA4fQVC7Db6vpK69jPiQMShpZ3JKCEjfYLUuN0slt
+FPhjiR175E0vTRuLoIj4kXNwLLswH0c9zqrKM2S92SCxAV3E4JJGKhUZalvT9s1g
+LrPhMCl6CsOES98T87d3RyAIK0iVRCnRUG3bc+8rzyRd4fzkAQKCAQEA/UjmCSm3
+H46t/1w7YBZPew7SFQOAJe81iDzbonc3fNPD2R8lxtD3MwdvrQ5f9fhT4+uveWNr
+dyBX7ppnissyM3LZRN+7BdeIVVeIPVen6Ou9W2i7q18ZoQx9IpRcZEw5tGJFZaGx
+EmyPN4i1K0ccUkGbBvbXXQ/tcG3wElRpBAc5/TQ8vrpUgHll2/MbYhowx6P9uHv5
+thoyG98X+7Fbg8ikzw5GtyuedXfyX1CpJ7yUQVS2PEaOMXOkZdx2bbWRAYYCpsqB
+dMmjs2PsFhZHu6CpLhlocHbfUiRztCUCaMZJPQXFSVmy8QDMvZEdVLvad9Poi8ny
+lmHVRgxaNbAtIQKCAQEA9nscqRaaO7hIX9bOUxcDbI0486Ws4H0hAFApIN+6/LP4
+hkxey3xWArTYWrvSG1d5GkJAdn99ayWzo2PevmJlrhIJiO1QqYBAk+87cnhwSCmB
+kb0sGkNWcc/xNRy7eqdhyCmVhaUnIbORee+cD6qiu/l2BAclTf2ZARFOGXjhQkvt
+cDbc/9ZR467ceXbiTIU34Be4xnNAY1mo59jvwl9eqxgpefYTqPhcZ7OmlDli77Hd
+XuRfuxLZCscv7A9M5Enc2zwOEP5VwRNwYzYtMm2Yh9CQZxNWC7JVh1Gw5MPFzsGl
+sgEdb4WGneN6PPLQHK7NF0f7wYSNnF0i3XSME9MumQKCAQEA0qMbWydr+TyJC0LC
+xigHtUkgAQXGPsXuePxTk4sdhBwAVcKHgg4qZi+a+gpoV4BLE9LfPU4nAwzM08to
+rI5Lk2nBsnt1Z2hVItQGoy0QoK3b7fbti5ktETf3oRhMtcSGgLLxD5ImVjId8Isq
+T3F15hpVOLdzZxtl1Qg4jKXSJ91yplYY5mzC9Yz/3qkQbsdlJcIFsLS5eG3UmkUw
+Bsr6VmA4X1F6Eb6eqwYzdHz6D+fOS36NhxcODaYkY+myO46xptixv8/NVTiTgQ5q
+OfwRb8Iur/3FUzIoioFyD7Bvjn7ITY1NArEsFS0bF9Nk1yDakKiUThyGN/Xojbac
+FuYKwQKCAQEAxOWJ+qU8phJLdowBHC0ZJiEWasRhep9auoZOpJ01IWOfV6EwZLs5
+dkYDQ1Agwoi5DDn6hu7HQM3IV/CS4mF2OnzcMw7ozc7PR53nTkVZ5LuLbuHAlmZO
+avKjDDucpJmLqjtV34IT5X8t6kt3zqgQAbuBBCy1Jz07ebfaPMzsnWpMDcU1/AW4
+OvrX0wweMOSGwzQP/i/ZMsRQAo2w0gQfeuv9Thk+kU99ebXwjx3co//hCEnFE4s1
+6L8/0AJU+VTr4hJyZi7WUDt4HzkLF+qm22/Hux+eMA/Q9R1UAxtFLCpTdAQiAJGY
+/Q3X+1I434DgAwYU3f1Gpq9cB65vq/KamQKCAQEAjIub5wde/ttHlLALvnOXrbqe
+nUIfWHExMzhul/rkr8fFEJwij2nZUuN2EWUGzBWQQoNXw5QKHLZyPsyFUOa/P2BS
+osnffAa+sumL4k36E71xFdTVV5ExyTXZVB49sPmUpivP9gEucFFqDHKjGsF45dBF
++DZdykLUIv+/jQUzXGkZ5Wv/r52YUNho4EZdwnlJ2so7cxnsYnjW+c1nlp17tkq5
+DfwktkeD9iFzlaZ66vLoO44luaBm+lC3xM2sHinOTwbk0gvhJAIoLfkOYhpmGc8A
+4W/E1OHfVz6xqVDsMBFhRbQpHNkf8XZNqkIoqHVMTaMOJJlM+lb0+A9B8Bm/XA==
+-----END RSA PRIVATE KEY-----
diff --git a/apex/statsd/com.android.os.statsd.pk8 b/apex/statsd/com.android.os.statsd.pk8
new file mode 100644
index 0000000..49910f8
--- /dev/null
+++ b/apex/statsd/com.android.os.statsd.pk8
Binary files differ
diff --git a/apex/statsd/com.android.os.statsd.x509.pem b/apex/statsd/com.android.os.statsd.x509.pem
new file mode 100644
index 0000000..e7b16b2
--- /dev/null
+++ b/apex/statsd/com.android.os.statsd.x509.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFDTCCAvWgAwIBAgIUCnta1LAl5fMMLLQx//4zWz9A2A8wDQYJKoZIhvcNAQEL
+BQAwFTETMBEGA1UECgwKR29vZ2xlIExMQzAgFw0xOTA4MTIyMjM5MzBaGA80NzU3
+MDcwODIyMzkzMFowFTETMBEGA1UECgwKR29vZ2xlIExMQzCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAOranWZ19jkXCF9WIlXv01tUUvLKMHWKV7X9Earw
+cL7/aax0pFbNJutgyBUiOszbR+0T7quZxz6jACu+6y7iaMJnvMluZsfTi+p2UvQt
+y6Ql7ZUOQ7bVluCFIW5hZ+8d9RrLmZdvX1r4YfF6HufDBkAbj+os+y6407OezJAV
+8EATpemc9gsCC4RJZpwzTs1RUXMD4UoNrLZAE8+7iaJZeBxmz0MAPj92pYc9M7/d
+xInzYvOR08/uEpHt8jlMdVgSQS/FaRlIOIqcGBk3cjkjDlpVATQ4Hyjy+IPQPjTD
+bJUmDJiYeBCyY/pYZQvTQjl8s+fvykTsF9Lfb+E+PhZ0+N8pRi7sUSpisZHSiqaN
+W3oxYWc0YQSuzygHHog8HH/azHX5L805g/+Rwfb/cUF9eJgjq0vrkFnsz4UKgKNV
+hHL90mfqpbc2UvJ8VY8BvIjbsHQ77LrBKlqI9VMPorttpTOuwHHJPKsyN972F0Ul
+lRB6CwFE8csVGWXoNaDZWBv7xTDdbdirmlKDNueg9pw6ksYV2Is9Dv8PxmsZvb+4
+oftC/hb4X1Pudn01PPs9Tx44CwHuVLENUwlDEVzG5zNetsv9kAuCYt3VRVF+NYqj
+NAfLbxCKLe25wGzJrZUEJ1YrYIjpUbfwnttEad/9Pu13DAS7HZwn5vwqEKB/1LlT
+NSUXAgMBAAGjUzBRMB0GA1UdDgQWBBSKElkhJSbzgh8+iysye8SrkmJ62DAfBgNV
+HSMEGDAWgBSKElkhJSbzgh8+iysye8SrkmJ62DAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4ICAQANFGnc2wJBrFbh2nzhl06g4TjPKGRCw365vZ1A3T9O
+jXP0lToHDxB33TpKk6d7zszR1uPphQQxgzhSVZB/jx8q4kWSSoKoF9Dlx7h8rAt+
+2TM5DaBvxrwu5mqOALwQuF81wap1Pl2L2fFHvygCm8b+Ci4iS5vcr0axNnp1rK1b
+vUtRWY4mfxTjJYcgeCVUGskqTb+cCxQZ6Icno6VTKajT1FybRmD3KZJaUuLbNEN+
+IE4nGTMG2WZ5Hl2vR8JJp1sYYn8T3ElMAb0MSNFkqsfI+tToEwGsuJDgYEdtEnzf
+lTycQvn5NhrIZRRN3pqSyWpAU7p9mmyTK0PHMz2D/Rtfb7lE692vXzxCmZND51mc
+YXCCoanV6eZZ7Sbqzh60+5QV38hgFBst5l8CcFaWWSFK9nBWdzS5lhs9lmQ4aiYd
+IE0qsNZgMob+TTP1VW39hu4EDjNmOrKfimM9J2tcPZ5QP01DgETPvAsB7vn2Xz9J
+HGt5ntiSV4W2izDP8viQ1M5NvfdBaUhcnNsE6/sxfU0USRs2hrEp1oiqrv4p6V0P
+qOt7C2/YtJzkrxfsHZAxBUSRHa7LwtzgeiJDUivHn94VnAzSAH8MLx6CzDPQ8HWN
+NiZFxTKfMVyjEmbQ2PalHWB8pWtpdEh7X4rzaqhnLBTis3pGssASgo3ArLIYleAU
++g==
+-----END CERTIFICATE-----
diff --git a/api/current.txt b/api/current.txt
index 6adddd0..140b6e0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8,6 +8,7 @@
   public static final class Manifest.permission {
     ctor public Manifest.permission();
     field public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
+    field public static final String ACCESSIBILITY_SHORTCUT_TARGET = "android.permission.ACCESSIBILITY_SHORTCUT_TARGET";
     field public static final String ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION";
     field public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
@@ -2812,12 +2813,12 @@
     method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
   }
 
-  public final class AccessibilityGestureInfo implements android.os.Parcelable {
+  public final class AccessibilityGestureEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getDisplayId();
     method public int getGestureId();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureInfo> CREATOR;
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR;
   }
 
   public abstract class AccessibilityService extends android.app.Service {
@@ -2836,7 +2837,7 @@
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     method public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated protected boolean onGesture(int);
-    method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureInfo);
+    method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
     method public abstract void onInterrupt();
     method protected boolean onKeyEvent(android.view.KeyEvent);
     method protected void onServiceConnected();
@@ -4279,14 +4280,21 @@
     method public void checkPackage(int, @NonNull String);
     method public void finishOp(@NonNull String, int, @NonNull String);
     method public boolean isOpActive(@NonNull String, int, @NonNull String);
-    method public int noteOp(@NonNull String, int, @NonNull String);
-    method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
-    method public int noteProxyOp(@NonNull String, @NonNull String);
-    method public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
-    method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+    method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
+    method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String);
+    method @Deprecated public int noteOpNoThrow(@NonNull String, int, @NonNull String);
+    method public int noteOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
+    method @Deprecated public int noteProxyOp(@NonNull String, @NonNull String);
+    method public int noteProxyOp(@NonNull String, @Nullable String, int, @Nullable String);
+    method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
+    method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+    method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String);
     method public static String permissionToOp(String);
-    method public int startOp(@NonNull String, int, @NonNull String);
-    method public int startOpNoThrow(@NonNull String, int, @NonNull String);
+    method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
+    method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
+    method public int startOp(@NonNull String, int, @Nullable String, @Nullable String);
+    method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
+    method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
     method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
     method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
     method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -4338,6 +4346,14 @@
     field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
   }
 
+  public abstract static class AppOpsManager.AppOpsCollector {
+    ctor public AppOpsManager.AppOpsCollector();
+    method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor();
+    method public abstract void onAsyncNoted(@NonNull android.app.AsyncNotedAppOp);
+    method public abstract void onNoted(@NonNull android.app.SyncNotedAppOp);
+    method public abstract void onSelfNoted(@NonNull android.app.SyncNotedAppOp);
+  }
+
   public static interface AppOpsManager.OnOpActiveChangedListener {
     method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
   }
@@ -4458,6 +4474,17 @@
     field public String serviceDetails;
   }
 
+  public final class AsyncNotedAppOp implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getMessage();
+    method @Nullable public String getNotingPackageName();
+    method @IntRange(from=0) public int getNotingUid();
+    method @NonNull public String getOp();
+    method @IntRange(from=0) public long getTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
+  }
+
   public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
     ctor public AuthenticationRequiredException(Throwable, android.app.PendingIntent);
     method public int describeContents();
@@ -5294,10 +5321,12 @@
     ctor public Notification(android.os.Parcel);
     method public android.app.Notification clone();
     method public int describeContents();
+    method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean);
     method public boolean getAllowSystemGeneratedContextualActions();
     method public int getBadgeIconType();
     method @Nullable public android.app.Notification.BubbleMetadata getBubbleMetadata();
     method public String getChannelId();
+    method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions();
     method public String getGroup();
     method public int getGroupAlertBehavior();
     method public android.graphics.drawable.Icon getLargeIcon();
@@ -5693,6 +5722,7 @@
     method public String getDataMimeType();
     method public android.net.Uri getDataUri();
     method public android.os.Bundle getExtras();
+    method @NonNull public static java.util.List<android.app.Notification.MessagingStyle.Message> getMessagesFromBundleArray(@Nullable android.os.Parcelable[]);
     method @Deprecated public CharSequence getSender();
     method @Nullable public android.app.Person getSenderPerson();
     method public CharSequence getText();
@@ -5790,6 +5820,7 @@
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
+    method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
     method public void setDescription(String);
@@ -6267,6 +6298,10 @@
   public class StatusBarManager {
   }
 
+  public final class SyncNotedAppOp {
+    method @NonNull public String getOp();
+  }
+
   @Deprecated public class TabActivity extends android.app.ActivityGroup {
     ctor @Deprecated public TabActivity();
     method @Deprecated public android.widget.TabHost getTabHost();
@@ -7182,18 +7217,18 @@
     method @Nullable public android.view.autofill.AutofillValue getAutofillValue();
     method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
     method public int getChildCount();
-    method public String getClassName();
-    method public CharSequence getContentDescription();
+    method @Nullable public String getClassName();
+    method @Nullable public CharSequence getContentDescription();
     method public float getElevation();
-    method public android.os.Bundle getExtras();
+    method @Nullable public android.os.Bundle getExtras();
     method public int getHeight();
-    method public String getHint();
+    method @Nullable public String getHint();
     method @Nullable public String getHintIdEntry();
     method @Nullable public android.view.ViewStructure.HtmlInfo getHtmlInfo();
     method public int getId();
-    method public String getIdEntry();
-    method public String getIdPackage();
-    method public String getIdType();
+    method @Nullable public String getIdEntry();
+    method @Nullable public String getIdPackage();
+    method @Nullable public String getIdType();
     method public int getImportantForAutofill();
     method public int getInputType();
     method public int getLeft();
@@ -7203,12 +7238,12 @@
     method public int getMinTextEms();
     method public int getScrollX();
     method public int getScrollY();
-    method public CharSequence getText();
+    method @Nullable public CharSequence getText();
     method public int getTextBackgroundColor();
     method public int getTextColor();
     method @Nullable public String getTextIdEntry();
-    method public int[] getTextLineBaselines();
-    method public int[] getTextLineCharOffsets();
+    method @Nullable public int[] getTextLineBaselines();
+    method @Nullable public int[] getTextLineCharOffsets();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
     method public float getTextSize();
@@ -7844,6 +7879,7 @@
     method public void deleteAppWidgetId(int);
     method public void deleteHost();
     method public int[] getAppWidgetIds();
+    method public void onAppWidgetRemoved(int);
     method protected android.appwidget.AppWidgetHostView onCreateView(android.content.Context, int, android.appwidget.AppWidgetProviderInfo);
     method protected void onProviderChanged(int, android.appwidget.AppWidgetProviderInfo);
     method protected void onProvidersChanged();
@@ -10875,9 +10911,7 @@
     method @Nullable public String getString(String, @Nullable String);
     method @Nullable public java.util.Set<java.lang.String> getStringSet(String, @Nullable java.util.Set<java.lang.String>);
     method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
-    method public default void registerOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener);
     method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener);
-    method public default void unregisterOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener);
   }
 
   public static interface SharedPreferences.Editor {
@@ -10897,10 +10931,6 @@
     method public void onSharedPreferenceChanged(android.content.SharedPreferences, String);
   }
 
-  public static interface SharedPreferences.OnSharedPreferencesClearListener {
-    method public void onSharedPreferencesClear(@NonNull android.content.SharedPreferences, @NonNull java.util.Set<java.lang.String>);
-  }
-
   public class SyncAdapterType implements android.os.Parcelable {
     ctor public SyncAdapterType(String, String, boolean, boolean);
     ctor public SyncAdapterType(android.os.Parcel);
@@ -16878,8 +16908,13 @@
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract String getId();
     method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
+    method public void setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException;
+    field public static final int AUDIO_RESTRICTION_NONE = 0; // 0x0
+    field public static final int AUDIO_RESTRICTION_VIBRATION = 1; // 0x1
+    field public static final int AUDIO_RESTRICTION_VIBRATION_SOUND = 3; // 0x3
     field public static final int TEMPLATE_MANUAL = 6; // 0x6
     field public static final int TEMPLATE_PREVIEW = 1; // 0x1
     field public static final int TEMPLATE_RECORD = 3; // 0x3
@@ -23934,6 +23969,7 @@
     method @Nullable public long[] getThumbnailRange();
     method public boolean hasAttribute(@NonNull String);
     method public boolean hasThumbnail();
+    method public static boolean isSupportedMimeType(@NonNull String);
     method public boolean isThumbnailCompressed();
     method public void saveAttributes() throws java.io.IOException;
     method public void setAttribute(@NonNull String, @Nullable String);
@@ -28204,7 +28240,7 @@
     method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
     method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
     method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
-    method public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
     method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
     method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
     method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
@@ -29428,7 +29464,8 @@
   }
 
   public class AudioGroup {
-    ctor public AudioGroup();
+    ctor @Deprecated public AudioGroup();
+    ctor public AudioGroup(@Nullable android.content.Context);
     method public void clear();
     method public int getMode();
     method public android.net.rtp.AudioStream[] getStreams();
@@ -41708,6 +41745,7 @@
     method public int getUid();
     method public android.os.UserHandle getUser();
     method @Deprecated public int getUserId();
+    method public boolean isAppGroup();
     method public boolean isClearable();
     method public boolean isGroup();
     method public boolean isOngoing();
@@ -44082,7 +44120,7 @@
 
   public class CarrierConfigManager {
     method @Nullable public android.os.PersistableBundle getConfig();
-    method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(String, int);
+    method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
     method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method public void notifyConfigChangedForSubId(int);
@@ -44976,6 +45014,7 @@
     field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
     field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
     field public static final int DEFAULT_SUBSCRIPTION_ID = 2147483647; // 0x7fffffff
+    field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
     field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
@@ -50414,6 +50453,7 @@
     method public final int getScrollY();
     method @android.view.ViewDebug.ExportedProperty(category="drawing") @ColorInt public int getSolidColor();
     method @LayoutRes public int getSourceLayoutResId();
+    method @android.view.ViewDebug.ExportedProperty(category="accessibility") @Nullable public final CharSequence getStateDescription();
     method public android.animation.StateListAnimator getStateListAnimator();
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
@@ -50758,6 +50798,7 @@
     method public void setScrollbarFadingEnabled(boolean);
     method public void setSelected(boolean);
     method public void setSoundEffectsEnabled(boolean);
+    method public void setStateDescription(@Nullable CharSequence);
     method public void setStateListAnimator(android.animation.StateListAnimator);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
     method public void setSystemUiVisibility(int);
@@ -52112,6 +52153,7 @@
     field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
     field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
+    field public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64; // 0x40
     field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
     field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
     field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
@@ -52229,6 +52271,7 @@
     method @Nullable public CharSequence getPaneTitle();
     method public android.view.accessibility.AccessibilityNodeInfo getParent();
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
+    method @Nullable public CharSequence getStateDescription();
     method public CharSequence getText();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
@@ -52320,6 +52363,7 @@
     method public void setShowingHintText(boolean);
     method public void setSource(android.view.View);
     method public void setSource(android.view.View, int);
+    method public void setStateDescription(@Nullable CharSequence);
     method public void setText(CharSequence);
     method public void setTextEntryKey(boolean);
     method public void setTextSelection(int, int);
@@ -52632,7 +52676,7 @@
     method protected android.view.animation.Animation clone() throws java.lang.CloneNotSupportedException;
     method public long computeDurationHint();
     method protected void ensureInterpolator();
-    method @ColorInt public int getBackgroundColor();
+    method @Deprecated @ColorInt public int getBackgroundColor();
     method @Deprecated public boolean getDetachWallpaper();
     method public long getDuration();
     method public boolean getFillAfter();
@@ -52656,7 +52700,7 @@
     method public void restrictDuration(long);
     method public void scaleCurrentDuration(float);
     method public void setAnimationListener(android.view.animation.Animation.AnimationListener);
-    method public void setBackgroundColor(@ColorInt int);
+    method @Deprecated public void setBackgroundColor(@ColorInt int);
     method @Deprecated public void setDetachWallpaper(boolean);
     method public void setDuration(long);
     method public void setFillAfter(boolean);
@@ -53000,6 +53044,7 @@
     method @NonNull public android.view.autofill.AutofillId newAutofillId(@NonNull android.view.autofill.AutofillId, long);
     method @NonNull public final android.view.ViewStructure newViewStructure(@NonNull android.view.View);
     method @NonNull public final android.view.ViewStructure newVirtualViewStructure(@NonNull android.view.autofill.AutofillId, long);
+    method public final void notifySessionLifecycle(boolean);
     method public final void notifyViewAppeared(@NonNull android.view.ViewStructure);
     method public final void notifyViewDisappeared(@NonNull android.view.autofill.AutofillId);
     method public final void notifyViewTextChanged(@NonNull android.view.autofill.AutofillId, @Nullable CharSequence);
diff --git a/api/removed.txt b/api/removed.txt
index db784a8..74a9346 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -5,10 +5,6 @@
     method @Deprecated public static int getMaxNumPictureInPictureActions();
   }
 
-  public class KeyguardManager {
-    method @Deprecated public void dismissKeyguard(@NonNull android.app.Activity, @Nullable android.app.KeyguardManager.KeyguardDismissCallback, @Nullable android.os.Handler);
-  }
-
   public class Notification implements android.os.Parcelable {
     method @Deprecated public String getChannel();
     method public static Class<? extends android.app.Notification.Style> getNotificationStyleClass(String);
diff --git a/api/system-current.txt b/api/system-current.txt
index e752f7c..ced3b3c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -140,6 +140,7 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     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";
@@ -521,8 +522,6 @@
   }
 
   public class Notification implements android.os.Parcelable {
-    method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean);
-    method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions();
     field public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
     field public static final String CATEGORY_CAR_INFORMATION = "car_information";
     field public static final String CATEGORY_CAR_WARNING = "car_warning";
@@ -531,10 +530,6 @@
     field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400
   }
 
-  public static final class Notification.MessagingStyle.Message {
-    method @Nullable public static android.app.Notification.MessagingStyle.Message getMessageFromBundle(@NonNull android.os.Bundle);
-  }
-
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
     ctor public Notification.TvExtender();
     ctor public Notification.TvExtender(android.app.Notification);
@@ -1207,7 +1202,7 @@
 
   public static final class UsageEvents.Event {
     method public int getInstanceId();
-    method public String getNotificationChannelId();
+    method @Nullable public String getNotificationChannelId();
     method @Nullable public String getTaskRootClassName();
     method @Nullable public String getTaskRootPackageName();
     method public boolean isInstantApp();
@@ -1387,6 +1382,7 @@
     field public static final String STATS_MANAGER = "stats";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
+    field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
     field public static final String VR_SERVICE = "vrmanager";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -1668,7 +1664,8 @@
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
-    field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
+    field public static final int FLAG_PERMISSION_REVOKED_COMPAT = 8; // 0x8
+    field @Deprecated public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
     field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10
     field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
@@ -1736,7 +1733,7 @@
     method public void onPermissionsChanged(int);
   }
 
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -3029,6 +3026,26 @@
     field public static final int STATUS_OK = 0; // 0x0
   }
 
+  public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+    field @NonNull public final String description;
+    field public final int id;
+    field @NonNull public final String implementor;
+    field public final int maxBufferMs;
+    field public final int maxKeyphrases;
+    field public final int maxSoundModels;
+    field public final int maxUsers;
+    field public final int powerConsumptionMw;
+    field public final int recognitionModes;
+    field public final boolean returnsTriggerInEvent;
+    field public final boolean supportsCaptureTransition;
+    field public final boolean supportsConcurrentCapture;
+    field @NonNull public final java.util.UUID uuid;
+    field public final int version;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     method @Nullable public android.media.AudioFormat getCaptureFormat();
     method public int getCaptureSession();
@@ -3771,6 +3788,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
     method public int getDetectionServiceOperationsTimeout();
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @Nullable public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -4705,7 +4723,7 @@
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
-    method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
+    method @Deprecated @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>);
@@ -5500,7 +5518,7 @@
   public class UpdateEngine {
     ctor public UpdateEngine();
     method public void applyPayload(String, long, long, String[]);
-    method public void applyPayload(java.io.FileDescriptor, long, long, String[]);
+    method public void applyPayload(@NonNull java.io.FileDescriptor, long, long, @NonNull String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
@@ -5664,6 +5682,14 @@
 
 }
 
+package android.os.telephony {
+
+  public class TelephonyRegistryManager {
+    method public void notifyCarrierNetworkChange(boolean);
+  }
+
+}
+
 package android.permission {
 
   public final class PermissionControllerManager {
@@ -5692,6 +5718,7 @@
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onUpdateUserSensitive();
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
   }
 
@@ -6104,6 +6131,30 @@
     field public static final String WAIT_TIME_RETRY = "wait_time";
   }
 
+  public static final class Telephony.CellBroadcasts implements android.provider.BaseColumns {
+    field public static final String CID = "cid";
+    field public static final String CMAS_CATEGORY = "cmas_category";
+    field public static final String CMAS_CERTAINTY = "cmas_certainty";
+    field public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+    field public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+    field public static final String CMAS_SEVERITY = "cmas_severity";
+    field public static final String CMAS_URGENCY = "cmas_urgency";
+    field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DEFAULT_SORT_ORDER = "date DESC";
+    field public static final String DELIVERY_TIME = "date";
+    field public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+    field public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+    field public static final String LAC = "lac";
+    field public static final String LANGUAGE_CODE = "language";
+    field public static final String MESSAGE_BODY = "body";
+    field public static final String MESSAGE_FORMAT = "format";
+    field public static final String MESSAGE_PRIORITY = "priority";
+    field public static final String MESSAGE_READ = "read";
+    field public static final String PLMN = "plmn";
+    field public static final String SERIAL_NUMBER = "serial_number";
+    field public static final String SERVICE_CATEGORY = "service_category";
+  }
+
   public final class TimeZoneRulesDataContract {
     field public static final String AUTHORITY = "com.android.timezone";
   }
@@ -6665,10 +6716,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.SnoozeCriterion> CREATOR;
   }
 
-  public class StatusBarNotification implements android.os.Parcelable {
-    method public boolean isAppGroup();
-  }
-
 }
 
 package android.service.oemlock {
@@ -7199,6 +7246,13 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
   }
 
+  public class CallerInfo {
+    method @Nullable public android.net.Uri getContactDisplayPhotoUri();
+    method public long getContactId();
+    method @Nullable public String getName();
+    method @Nullable public String getPhoneNumber();
+  }
+
   public class CarrierConfigManager {
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
@@ -7691,6 +7745,24 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
+    method public int describeContents();
+    method public int getIdleTimeMillis();
+    method public int getReceiveTimeMillis();
+    method public int getSleepTimeMillis();
+    method public long getTimestamp();
+    method @NonNull public java.util.List<android.telephony.ModemActivityInfo.TransmitPower> getTransmitPowerInfo();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVELS = 5; // 0x5
+  }
+
+  public class ModemActivityInfo.TransmitPower {
+    method @NonNull public android.util.Range<java.lang.Integer> getPowerRangeInDbm();
+    method public int getTimeInMillis();
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getAccessNetworkTechnology();
@@ -7792,6 +7864,8 @@
     field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
@@ -7936,7 +8010,125 @@
     field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
   }
 
+  public final class SmsCbCmasInfo implements android.os.Parcelable {
+    ctor public SmsCbCmasInfo(int, int, int, int, int, int);
+    method public int describeContents();
+    method public int getCategory();
+    method public int getCertainty();
+    method public int getMessageClass();
+    method public int getResponseType();
+    method public int getSeverity();
+    method public int getUrgency();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CMAS_CATEGORY_CBRNE = 10; // 0xa
+    field public static final int CMAS_CATEGORY_ENV = 7; // 0x7
+    field public static final int CMAS_CATEGORY_FIRE = 5; // 0x5
+    field public static final int CMAS_CATEGORY_GEO = 0; // 0x0
+    field public static final int CMAS_CATEGORY_HEALTH = 6; // 0x6
+    field public static final int CMAS_CATEGORY_INFRA = 9; // 0x9
+    field public static final int CMAS_CATEGORY_MET = 1; // 0x1
+    field public static final int CMAS_CATEGORY_OTHER = 11; // 0xb
+    field public static final int CMAS_CATEGORY_RESCUE = 4; // 0x4
+    field public static final int CMAS_CATEGORY_SAFETY = 2; // 0x2
+    field public static final int CMAS_CATEGORY_SECURITY = 3; // 0x3
+    field public static final int CMAS_CATEGORY_TRANSPORT = 8; // 0x8
+    field public static final int CMAS_CATEGORY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CERTAINTY_LIKELY = 1; // 0x1
+    field public static final int CMAS_CERTAINTY_OBSERVED = 0; // 0x0
+    field public static final int CMAS_CERTAINTY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 3; // 0x3
+    field public static final int CMAS_CLASS_CMAS_EXERCISE = 5; // 0x5
+    field public static final int CMAS_CLASS_EXTREME_THREAT = 1; // 0x1
+    field public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 6; // 0x6
+    field public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0; // 0x0
+    field public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 4; // 0x4
+    field public static final int CMAS_CLASS_SEVERE_THREAT = 2; // 0x2
+    field public static final int CMAS_CLASS_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_RESPONSE_TYPE_ASSESS = 6; // 0x6
+    field public static final int CMAS_RESPONSE_TYPE_AVOID = 5; // 0x5
+    field public static final int CMAS_RESPONSE_TYPE_EVACUATE = 1; // 0x1
+    field public static final int CMAS_RESPONSE_TYPE_EXECUTE = 3; // 0x3
+    field public static final int CMAS_RESPONSE_TYPE_MONITOR = 4; // 0x4
+    field public static final int CMAS_RESPONSE_TYPE_NONE = 7; // 0x7
+    field public static final int CMAS_RESPONSE_TYPE_PREPARE = 2; // 0x2
+    field public static final int CMAS_RESPONSE_TYPE_SHELTER = 0; // 0x0
+    field public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_SEVERITY_EXTREME = 0; // 0x0
+    field public static final int CMAS_SEVERITY_SEVERE = 1; // 0x1
+    field public static final int CMAS_SEVERITY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_URGENCY_EXPECTED = 1; // 0x1
+    field public static final int CMAS_URGENCY_IMMEDIATE = 0; // 0x0
+    field public static final int CMAS_URGENCY_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbCmasInfo> CREATOR;
+  }
+
+  public final class SmsCbEtwsInfo implements android.os.Parcelable {
+    ctor public SmsCbEtwsInfo(int, boolean, boolean, boolean, @Nullable byte[]);
+    method public int describeContents();
+    method @Nullable public byte[] getPrimaryNotificationSignature();
+    method public long getPrimaryNotificationTimestamp();
+    method public int getWarningType();
+    method public boolean isEmergencyUserAlert();
+    method public boolean isPopupAlert();
+    method public boolean isPrimary();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbEtwsInfo> CREATOR;
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0; // 0x0
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 2; // 0x2
+    field public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 4; // 0x4
+    field public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 3; // 0x3
+    field public static final int ETWS_WARNING_TYPE_TSUNAMI = 1; // 0x1
+    field public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class SmsCbLocation implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCid();
+    method public int getLac();
+    method @NonNull public String getPlmn();
+    method public boolean isInLocationArea(@NonNull android.telephony.SmsCbLocation);
+    method public boolean isInLocationArea(@Nullable String, int, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbLocation> CREATOR;
+  }
+
+  public final class SmsCbMessage implements android.os.Parcelable {
+    ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo);
+    method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor);
+    method public int describeContents();
+    method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo();
+    method @NonNull public android.content.ContentValues getContentValues();
+    method @Nullable public android.telephony.SmsCbEtwsInfo getEtwsWarningInfo();
+    method public int getGeographicalScope();
+    method @Nullable public String getLanguageCode();
+    method @NonNull public android.telephony.SmsCbLocation getLocation();
+    method @Nullable public String getMessageBody();
+    method public int getMessageFormat();
+    method public int getMessagePriority();
+    method public long getReceivedTime();
+    method public int getSerialNumber();
+    method public int getServiceCategory();
+    method public boolean isCmasMessage();
+    method public boolean isEmergencyMessage();
+    method public boolean isEtwsMessage();
+    method public boolean needGeoFencingCheck();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbMessage> CREATOR;
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; // 0x3
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; // 0x0
+    field public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2; // 0x2
+    field public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP2 = 2; // 0x2
+    field public static final int MESSAGE_PRIORITY_EMERGENCY = 3; // 0x3
+    field public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; // 0x1
+    field public static final int MESSAGE_PRIORITY_NORMAL = 0; // 0x0
+    field public static final int MESSAGE_PRIORITY_URGENT = 2; // 0x2
+  }
+
   public final class SmsManager {
+    method public boolean disableCellBroadcastRange(int, int, int);
+    method public boolean enableCellBroadcastRange(int, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     field public static final int RESULT_CANCELLED = 23; // 0x17
     field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
@@ -7966,6 +8158,7 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+    method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -8020,7 +8213,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableModemForSlot(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getAidForAppType(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
@@ -8041,10 +8234,13 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public static long getMaxNumberVerificationTimeoutMillis();
+    method @NonNull public String getNetworkCountryIso(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
     method public int getSimCardState();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
@@ -8057,6 +8253,7 @@
     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 String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
     method @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 isApplicationOnUicc(int);
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -8064,7 +8261,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
@@ -8488,6 +8685,7 @@
     method public android.os.Bundle getCallExtras();
     method public int getCallType();
     method public static int getCallTypeFromVideoState(int);
+    method public int getCallerNumberVerificationStatus();
     method public int getEmergencyCallRouting();
     method public int getEmergencyServiceCategories();
     method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
@@ -8505,6 +8703,7 @@
     method public void setCallExtraBoolean(String, boolean);
     method public void setCallExtraInt(String, int);
     method public void setCallRestrictCause(int);
+    method public void setCallerNumberVerificationStatus(int);
     method public void setEmergencyCallRouting(int);
     method public void setEmergencyCallTesting(boolean);
     method public void setEmergencyServiceCategories(int);
@@ -8555,6 +8754,9 @@
     field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2
     field public static final int SERVICE_TYPE_NONE = 0; // 0x0
     field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1
+    field public static final int VERIFICATION_STATUS_FAILED = 2; // 0x2
+    field public static final int VERIFICATION_STATUS_NOT_VERIFIED = 0; // 0x0
+    field public static final int VERIFICATION_STATUS_PASSED = 1; // 0x1
   }
 
   public class ImsCallSessionListener {
@@ -9153,7 +9355,7 @@
     field public static final int STATE_UNAVAILABLE = 0; // 0x0
   }
 
-  public static class ImsFeature.Capabilities {
+  @Deprecated public static class ImsFeature.Capabilities {
     field @Deprecated protected int mCapabilities;
   }
 
@@ -9187,7 +9389,7 @@
   public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
     ctor public MmTelFeature.MmTelCapabilities();
     ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
-    ctor public MmTelFeature.MmTelCapabilities(int);
+    ctor public MmTelFeature.MmTelCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void addCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void removeCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
@@ -9329,14 +9531,18 @@
     method public void acknowledgeSmsReport(int, int, int);
     method public String getSmsFormat();
     method public void onReady();
-    method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
+    method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
+    method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException;
+    method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException;
     method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
-    method public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+    method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+    method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
     method public void sendSms(int, int, String, String, boolean, byte[]);
     field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
     field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
     field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
     field public static final int DELIVER_STATUS_OK = 1; // 0x1
+    field public static final int RESULT_NO_NETWORK_ERROR = -1; // 0xffffffff
     field public static final int SEND_STATUS_ERROR = 2; // 0x2
     field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4
     field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3
diff --git a/api/test-current.txt b/api/test-current.txt
index 7e1c67d..61bdc96 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21,6 +21,10 @@
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
+  public static final class Manifest.permission_group {
+    field public static final String UNDEFINED = "android.permission-group.UNDEFINED";
+  }
+
   public static final class R.bool {
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
   }
@@ -34,8 +38,8 @@
 
 package android.accessibilityservice {
 
-  public final class AccessibilityGestureInfo implements android.os.Parcelable {
-    ctor public AccessibilityGestureInfo(int, int);
+  public final class AccessibilityGestureEvent implements android.os.Parcelable {
+    ctor public AccessibilityGestureEvent(int, int);
   }
 
 }
@@ -82,6 +86,7 @@
   }
 
   public class ActivityOptions {
+    method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
     method public void setLaunchTaskId(int);
     method public void setLaunchWindowingMode(int);
@@ -96,8 +101,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeDockedStack(android.graphics.Rect, android.graphics.Rect);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeStack(int, android.graphics.Rect, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizePinnedStack(int, android.graphics.Rect, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeTask(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setDisplayToSingleTaskInstance(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
@@ -619,7 +623,6 @@
     method public int describeContents();
     method public static android.content.AutofillOptions forWhitelistingItself();
     method public boolean isAugmentedAutofillEnabled(@NonNull android.content.Context);
-    method public boolean isAutofillDisabledLocked(@NonNull android.content.ComponentName);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.AutofillOptions> CREATOR;
     field public long appDisabledExpiration;
@@ -740,7 +743,8 @@
     field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
     field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
-    field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
+    field public static final int FLAG_PERMISSION_REVOKED_COMPAT = 8; // 0x8
+    field @Deprecated public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
     field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10
     field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2
@@ -921,6 +925,10 @@
     field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
   }
 
+  public final class CameraManager {
+    method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
+  }
+
 }
 
 package android.hardware.display {
@@ -2371,6 +2379,7 @@
     field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
     field public static final String LOW_POWER_MODE = "low_power";
     field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
+    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
     field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
@@ -2394,7 +2403,7 @@
     field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
     field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
     field public static final String NOTIFICATION_BADGING = "notification_badging";
-    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+    field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
@@ -2903,6 +2912,7 @@
     method public int checkCarrierPrivilegesForPackage(String);
     method public int getCarrierIdListVersion();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
+    method @NonNull public String getNetworkCountryIso(int);
     method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
@@ -3335,6 +3345,11 @@
     field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x200, equals=0x200, name="INHERIT_TRANSLUCENT_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC")}) public int privateFlags;
   }
 
+  public class WindowlessViewRoot {
+    ctor public WindowlessViewRoot(android.content.Context, android.view.Display, android.view.SurfaceControl);
+    method public void addView(android.view.View, android.view.WindowManager.LayoutParams);
+  }
+
 }
 
 package android.view.accessibility {
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index f925023..8be95e4 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -22,7 +22,6 @@
         "libcutils",
         "libdl",
         "libhidlbase",
-        "libhwbinder",
         "liblog",
         "libnativeloader",
         "libutils",
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index db384ba..e1cb7ca 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -288,6 +288,48 @@
             dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
 
     SurfaceComposerClient::Transaction t;
+
+    // this guest property specifies multi-display IDs to show the boot animation
+    // multiple ids can be set with comma (,) as separator, for example:
+    // setprop boot.animation.displays 19260422155234049,19261083906282754
+    Vector<uint64_t> physicalDisplayIds;
+    char displayValue[PROPERTY_VALUE_MAX] = "";
+    property_get("boot.animation.displays", displayValue, "");
+    bool isValid = displayValue[0] != '\0';
+    if (isValid) {
+        char *p = displayValue;
+        while (*p) {
+            if (!isdigit(*p) && *p != ',') {
+                isValid = false;
+                break;
+            }
+            p ++;
+        }
+        if (!isValid)
+            SLOGE("Invalid syntax for the value of system prop: boot.animation.displays");
+    }
+    if (isValid) {
+        std::istringstream stream(displayValue);
+        for (PhysicalDisplayId id; stream >> id; ) {
+            physicalDisplayIds.add(id);
+            if (stream.peek() == ',')
+                stream.ignore();
+        }
+
+        // In the case of multi-display, boot animation shows on the specified displays
+        // in addition to the primary display
+        auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        constexpr uint32_t LAYER_STACK = 0;
+        for (auto id : physicalDisplayIds) {
+            if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
+                sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(id);
+                if (token != nullptr)
+                    t.setDisplayLayerStack(token, LAYER_STACK);
+            }
+        }
+        t.setLayerStack(control, LAYER_STACK);
+    }
+
     t.setLayer(control, 0x40000000)
         .apply();
 
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index d4d5871..4c77ba4 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -69,6 +69,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libutils",
                 "libziparchive",
             ],
@@ -121,6 +122,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
@@ -163,6 +165,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 0b349e1..053b7bc 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -98,6 +98,7 @@
 }
 
 std::vector<std::string> PoliciesForPath(const std::string& apk_path) {
+  // clang-format off
   static const std::vector<std::pair<std::string, std::string>> values = {
       {"/odm/", kPolicyOdm},
       {"/oem/", kPolicyOem},
@@ -106,6 +107,7 @@
       {"/system_ext/", kPolicySystem},
       {"/vendor/", kPolicyVendor},
   };
+  // clang-format on
 
   std::vector<std::string> fulfilled_policies = {kPolicyPublic};
   for (auto const& pair : values) {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 1aab059..94d2af4 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -34,18 +34,19 @@
   }
 
   binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
-                              std::string* _aidl_return);
+                              std::string* _aidl_return) override;
 
   binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
-                             bool* _aidl_return);
+                             bool* _aidl_return) override;
 
   binder::Status verifyIdmap(const std::string& overlay_apk_path, int32_t fulfilled_policies,
-                             bool enforce_overlayable, int32_t user_id, bool* _aidl_return);
+                             bool enforce_overlayable, int32_t user_id,
+                             bool* _aidl_return) override;
 
   binder::Status createIdmap(const std::string& target_apk_path,
                              const std::string& overlay_apk_path, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
-                             std::unique_ptr<std::string>* _aidl_return);
+                             std::unique_ptr<std::string>* _aidl_return) override;
 };
 
 }  // namespace android::os
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 2c3e9d3..1a0d443 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -29,11 +29,12 @@
  public:
   explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~BinaryStreamVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   void Write16(uint16_t value);
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 5111bb2..f0f141a 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -33,11 +33,12 @@
  public:
   explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~PrettyPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   std::ostream& stream_;
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 2e543d4..cd38971 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -34,11 +34,12 @@
  public:
   explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~RawPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   void print(uint16_t value, const char* fmt, ...);
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index adea329..a7c2f28 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -123,7 +123,7 @@
 
 class Idmap2Tests : public testing::Test {
  protected:
-  virtual void SetUp() {
+  void SetUp() override {
 #ifdef __ANDROID__
     tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX";
 #else
@@ -136,7 +136,7 @@
     idmap_path_ = tmp_dir_path_ + "/a.idmap";
   }
 
-  virtual void TearDown() {
+  void TearDown() override {
     EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0)
         << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno);
   }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 19fa640..05ff490 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -51,11 +51,6 @@
     srcs: [
         ":statsd_aidl",
         "src/active_config_list.proto",
-        "src/statsd_config.proto",
-        "src/uid_data.proto",
-        "src/FieldValue.cpp",
-        "src/hash.cpp",
-        "src/stats_log_util.cpp",
         "src/anomaly/AlarmMonitor.cpp",
         "src/anomaly/AlarmTracker.cpp",
         "src/anomaly/AnomalyTracker.cpp",
@@ -63,51 +58,58 @@
         "src/anomaly/subscriber_util.cpp",
         "src/condition/CombinationConditionTracker.cpp",
         "src/condition/condition_util.cpp",
-        "src/condition/SimpleConditionTracker.cpp",
         "src/condition/ConditionWizard.cpp",
-        "src/condition/StateTracker.cpp",
+        "src/condition/SimpleConditionTracker.cpp",
+        "src/condition/StateConditionTracker.cpp",
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
         "src/external/GpuStatsPuller.cpp",
         "src/external/Perfetto.cpp",
-        "src/external/StatsPuller.cpp",
+        "src/external/PowerStatsPuller.cpp",
+        "src/external/puller_util.cpp",
+        "src/external/ResourceHealthManagerPuller.cpp",
         "src/external/StatsCallbackPuller.cpp",
         "src/external/StatsCompanionServicePuller.cpp",
-        "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/PowerStatsPuller.cpp",
-        "src/external/ResourceHealthManagerPuller.cpp",
-        "src/external/TrainInfoPuller.cpp",
+        "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
-        "src/external/puller_util.cpp",
+        "src/external/SubsystemSleepStatePuller.cpp",
+        "src/external/TrainInfoPuller.cpp",
+        "src/FieldValue.cpp",
+        "src/guardrail/StatsdStats.cpp",
+        "src/hash.cpp",
+        "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
         "src/matchers/CombinationLogMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
         "src/matchers/SimpleLogMatchingTracker.cpp",
-        "src/metrics/MetricProducer.cpp",
-        "src/metrics/EventMetricProducer.cpp",
         "src/metrics/CountMetricProducer.cpp",
-        "src/metrics/DurationMetricProducer.cpp",
-        "src/metrics/duration_helper/OringDurationTracker.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
-        "src/metrics/ValueMetricProducer.cpp",
+        "src/metrics/duration_helper/OringDurationTracker.cpp",
+        "src/metrics/DurationMetricProducer.cpp",
+        "src/metrics/EventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
-        "src/metrics/MetricsManager.cpp",
+        "src/metrics/MetricProducer.cpp",
         "src/metrics/metrics_manager_util.cpp",
+        "src/metrics/MetricsManager.cpp",
+        "src/metrics/ValueMetricProducer.cpp",
         "src/packages/UidMap.cpp",
-        "src/storage/StorageManager.cpp",
+        "src/shell/shell_config.proto",
+        "src/shell/ShellSubscriber.cpp",
+        "src/socket/StatsSocketListener.cpp",
+        "src/state/StateManager.cpp",
+        "src/state/StateTracker.cpp",
+        "src/stats_log_util.cpp",
+        "src/statscompanion_util.cpp",
+        "src/statsd_config.proto",
         "src/StatsLogProcessor.cpp",
         "src/StatsService.cpp",
-        "src/statscompanion_util.cpp",
+        "src/storage/StorageManager.cpp",
         "src/subscriber/IncidentdReporter.cpp",
         "src/subscriber/SubscriberReporter.cpp",
-        "src/HashableDimensionKey.cpp",
-        "src/guardrail/StatsdStats.cpp",
-        "src/socket/StatsSocketListener.cpp",
-        "src/shell/ShellSubscriber.cpp",
-        "src/shell/shell_config.proto",
+        "src/uid_data.proto",
     ],
 
     local_include_dirs: [
@@ -120,23 +122,23 @@
     ],
 
     shared_libs: [
-        "libbase",
-        "libbinder",
-        "libgraphicsenv",
-        "libincident",
-        "liblog",
-        "libutils",
-        "libservices",
-        "libprotoutil",
-        "libstatslog",
-        "libhidlbase",
         "android.frameworks.stats@1.0",
         "android.hardware.health@2.0",
+        "android.hardware.power.stats@1.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power.stats@1.0",
-        "libsysutils",
+        "libbase",
+        "libbinder",
         "libcutils",
+        "libgraphicsenv",
+        "libhidlbase",
+        "libincident",
+        "liblog",
+        "libprotoutil",
+        "libservices",
+        "libstatslog",
+        "libsysutils",
+        "libutils",
     ],
 }
 
@@ -204,56 +206,61 @@
     ],
 
     srcs: [
+        // atom_field_options.proto needs field_options.proto, but that is
+        // not included in libprotobuf-cpp-lite, so compile it here.
+        ":libprotobuf-internal-protos",
+
         "src/atom_field_options.proto",
         "src/atoms.proto",
-        "src/stats_log.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
         "tests/AlarmMonitor_test.cpp",
         "tests/anomaly/AlarmTracker_test.cpp",
         "tests/anomaly/AnomalyTracker_test.cpp",
+        "tests/condition/CombinationConditionTracker_test.cpp",
+        "tests/condition/ConditionTimer_test.cpp",
+        "tests/condition/SimpleConditionTracker_test.cpp",
+        "tests/condition/StateConditionTracker_test.cpp",
         "tests/ConfigManager_test.cpp",
-        "tests/external/puller_util_test.cpp",
+        "tests/e2e/Alarm_e2e_test.cpp",
+        "tests/e2e/Anomaly_count_e2e_test.cpp",
+        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
+        "tests/e2e/Attribution_e2e_test.cpp",
+        "tests/e2e/ConfigTtl_e2e_test.cpp",
+        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
+        "tests/e2e/MetricActivation_e2e_test.cpp",
+        "tests/e2e/MetricConditionLink_e2e_test.cpp",
+        "tests/e2e/PartialBucket_e2e_test.cpp",
+        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
+        "tests/e2e/WakelockDuration_e2e_test.cpp",
         "tests/external/GpuStatsPuller_test.cpp",
         "tests/external/IncidentReportArgs_test.cpp",
+        "tests/external/puller_util_test.cpp",
         "tests/external/StatsPuller_test.cpp",
+        "tests/FieldValue_test.cpp",
+        "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
+        "tests/log_event/LogEventQueue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
         "tests/LogEvent_test.cpp",
-        "tests/log_event/LogEventQueue_test.cpp",
-        "tests/MetricsManager_test.cpp",
-        "tests/StatsLogProcessor_test.cpp",
-        "tests/StatsService_test.cpp",
-        "tests/UidMap_test.cpp",
-        "tests/FieldValue_test.cpp",
-        "tests/condition/CombinationConditionTracker_test.cpp",
-        "tests/condition/SimpleConditionTracker_test.cpp",
-        "tests/condition/StateTracker_test.cpp",
-        "tests/condition/ConditionTimer_test.cpp",
-        "tests/metrics/OringDurationTracker_test.cpp",
-        "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/CountMetricProducer_test.cpp",
         "tests/metrics/DurationMetricProducer_test.cpp",
         "tests/metrics/EventMetricProducer_test.cpp",
-        "tests/metrics/ValueMetricProducer_test.cpp",
         "tests/metrics/GaugeMetricProducer_test.cpp",
-        "tests/guardrail/StatsdStats_test.cpp",
+        "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/metrics_test_helper.cpp",
-        "tests/statsd_test_util.cpp",
-        "tests/storage/StorageManager_test.cpp",
-        "tests/e2e/WakelockDuration_e2e_test.cpp",
-        "tests/e2e/MetricActivation_e2e_test.cpp",
-        "tests/e2e/MetricConditionLink_e2e_test.cpp",
-        "tests/e2e/Alarm_e2e_test.cpp",
-        "tests/e2e/Attribution_e2e_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
-        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
-        "tests/e2e/Anomaly_count_e2e_test.cpp",
-        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
-        "tests/e2e/ConfigTtl_e2e_test.cpp",
-        "tests/e2e/PartialBucket_e2e_test.cpp",
-        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/metrics/OringDurationTracker_test.cpp",
+        "tests/metrics/ValueMetricProducer_test.cpp",
+        "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
+        "tests/state/StateTracker_test.cpp",
+        "tests/statsd_test_util.cpp",
+        "tests/StatsLogProcessor_test.cpp",
+        "tests/StatsService_test.cpp",
+        "tests/storage/StorageManager_test.cpp",
+        "tests/UidMap_test.cpp",
     ],
 
     static_libs: [
@@ -262,11 +269,11 @@
     ],
 
     proto: {
-        type: "full",
+        type: "lite",
         include_dirs: ["external/protobuf/src"],
     },
 
-    shared_libs: ["libprotobuf-cpp-full"],
+    shared_libs: ["libprotobuf-cpp-lite"],
 
 }
 
@@ -279,21 +286,25 @@
     defaults: ["statsd_defaults"],
 
     srcs: [
+        // atom_field_options.proto needs field_options.proto, but that is
+        // not included in libprotobuf-cpp-lite, so compile it here.
+        ":libprotobuf-internal-protos",
+
+        "benchmark/duration_metric_benchmark.cpp",
+        "benchmark/filter_value_benchmark.cpp",
+        "benchmark/get_dimensions_for_condition_benchmark.cpp",
+        "benchmark/hello_world_benchmark.cpp",
+        "benchmark/log_event_benchmark.cpp",
+        "benchmark/main.cpp",
+        "benchmark/metric_util.cpp",
+        "benchmark/stats_write_benchmark.cpp",
         "src/atom_field_options.proto",
         "src/atoms.proto",
         "src/stats_log.proto",
-        "benchmark/main.cpp",
-        "benchmark/hello_world_benchmark.cpp",
-        "benchmark/log_event_benchmark.cpp",
-        "benchmark/stats_write_benchmark.cpp",
-        "benchmark/filter_value_benchmark.cpp",
-        "benchmark/get_dimensions_for_condition_benchmark.cpp",
-        "benchmark/metric_util.cpp",
-        "benchmark/duration_metric_benchmark.cpp",
     ],
 
     proto: {
-        type: "full",
+        type: "lite",
         include_dirs: ["external/protobuf/src"],
     },
 
@@ -315,7 +326,7 @@
     shared_libs: [
         "libgtest_prod",
         "libstatslog",
-        "libprotobuf-cpp-full",
+        "libprotobuf-cpp-lite",
     ],
 }
 
@@ -329,11 +340,11 @@
     },
 
     srcs: [
-        "src/stats_log.proto",
-        "src/statsd_config.proto",
         "src/atoms.proto",
         "src/shell/shell_config.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
+        "src/statsd_config.proto",
     ],
 
     static_libs: [
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 1185127..84a0607 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -116,28 +116,13 @@
 }
 
 bool isAttributionUidField(const FieldValue& value) {
-    int field = value.mField.getField() & 0xff007f;
-    if (field == 0x10001 && value.mValue.getType() == INT) {
-        return true;
-    }
-    return false;
+    return isAttributionUidField(value.mField, value.mValue);
 }
 
 int32_t getUidIfExists(const FieldValue& value) {
-    bool isUid = false;
     // the field is uid field if the field is the uid field in attribution node or marked as
     // is_uid in atoms.proto
-    if (isAttributionUidField(value)) {
-        isUid = true;
-    } else {
-        auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag());
-        if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-            int uidField = it->second;  // uidField is the field number in proto
-            isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField &&
-                    value.mValue.getType() == INT;
-        }
-    }
-
+    bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue);
     return isUid ? value.mValue.int_value : -1;
 }
 
@@ -153,7 +138,7 @@
     auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag());
 
     if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-        int uidField = it->second;
+        int uidField = it->second;  // uidField is the field number in proto
         return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField &&
                value.getType() == INT;
     }
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index af8b3af..5e156bb 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -59,6 +59,16 @@
     return JenkinsHashWhiten(hash);
 }
 
+bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values, Value* output) {
+    for (const auto& value : values) {
+        if (value.mField.matches(matcherField)) {
+            (*output) = value.mValue;
+            return true;
+        }
+    }
+    return false;
+}
+
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
                   HashableDimensionKey* output) {
     size_t num_matches = 0;
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 6f4941f..a123850 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -120,6 +120,13 @@
 android::hash_t hashDimension(const HashableDimensionKey& key);
 
 /**
+ * Returns true if a FieldValue field matches the matcher field.
+ * The value of the FieldValue is output.
+ */
+bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values,
+                  Value* output);
+
+/**
  * Creating HashableDimensionKeys from FieldValues using matcher.
  *
  * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
@@ -169,4 +176,4 @@
         return android::JenkinsHashWhiten(hash);
     }
 };
-}  // namespace std
\ No newline at end of file
+}  // namespace std
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1c7180f..b665a8b 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -266,7 +266,9 @@
                     IResultReceiver::asInterface(data.readStrongBinder());
 
             err = command(in, out, err, args, resultReceiver);
-            resultReceiver->send(err);
+            if (resultReceiver != nullptr) {
+                resultReceiver->send(err);
+            }
             return NO_ERROR;
         }
         default: { return BnStatsManager::onTransact(code, data, reply, flags); }
@@ -411,13 +413,20 @@
             return cmd_trigger_active_config_broadcast(out, args);
         }
         if (!args[0].compare(String8("data-subscribe"))) {
-            if (mShellSubscriber == nullptr) {
-                mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+            {
+                std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
+                if (mShellSubscriber == nullptr) {
+                    mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
+                }
             }
             int timeoutSec = -1;
             if (argCount >= 2) {
                 timeoutSec = atoi(args[1].c_str());
             }
+            if (resultReceiver == nullptr) {
+                ALOGI("Null resultReceiver given, no subscription will be started");
+                return UNEXPECTED_NULL;
+            }
             mShellSubscriber->startNewSubscription(in, out, resultReceiver, timeoutSec);
             return NO_ERROR;
         }
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 53b6ce9..9490948 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -432,6 +432,10 @@
 
     sp<ShellSubscriber> mShellSubscriber;
 
+    /**
+     * Mutex for setting the shell subscriber
+     */
+    mutable mutex mShellSubscriberMutex;
     std::shared_ptr<LogEventQueue> mEventQueue;
 
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index d1dcb5df..7ace44e 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -24,6 +24,7 @@
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
+#include <inttypes.h>
 #include <statslog.h>
 #include <time.h>
 
@@ -224,7 +225,7 @@
     }
 
     if (!mSubscriptions.empty()) {
-        ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.",
+        ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.",
                 mAlert.id(), key.toString().c_str());
         informSubscribers(key, metricId, metricValue);
     } else {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index b01b0a8..b71a86b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -334,10 +334,13 @@
         BackGesture back_gesture_reported_reported = 224;
         UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225;
         UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226;
+        CameraActionEvent camera_action_event = 227;
+        AppCompatibilityChangeReported app_compatibility_change_reported =
+            228 [(allow_from_any_uid) = true];
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10062
+    // Next: 10065
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -401,6 +404,9 @@
         CoolingDevice cooling_device = 10059;
         AppOps app_ops = 10060;
         ProcessSystemIonHeapSize process_system_ion_heap_size = 10061;
+        SurfaceflingerStatsGlobalInfo surfaceflinger_stats_global_info = 10062;
+        SurfaceflingerStatsLayerInfo surfaceflinger_stats_layer_info = 10063;
+        ProcessMemorySnapshot process_memory_snapshot = 10064;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -2615,12 +2621,14 @@
 message TouchEventReported {
     /**
      * The fields latency_{min|max|mean|stdev} represent minimum, maximum, mean,
-     * and the standard deviation of latency between the kernel and framework
-     * for touchscreen events. The units are microseconds.
+     * and the standard deviation of the time spent processing touchscreen events
+     * in the kernel and inputflinger. The units are microseconds.
      *
-     * The number is measured as the difference between the time at which
-     * the input event was received in the evdev driver,
-     * and the time at which the input event was received in EventHub.
+     * On supported devices, the starting point is taken during the hard interrupt inside the
+     * kernel touch driver. On all other devices, the starting point is taken inside
+     * the kernel's input event subsystem upon receipt of the input event.
+     * The ending point is taken inside InputDispatcher, just after the input event
+     * is sent to the app.
      */
     // Minimum value
     optional float latency_min_micros = 1;
@@ -3062,9 +3070,9 @@
  *     services/core/java/com/android/server/wm/Session.java
  */
 message OverlayStateChanged {
-    optional int32 uid = 1 [(is_uid) = true];
+    optional int32 uid = 1 [(state_field_option).option = PRIMARY, (is_uid) = true];
 
-    optional string package_name = 2;
+    optional string package_name = 2 [(state_field_option).option = PRIMARY];
 
     optional bool using_alert_window = 3;
 
@@ -3072,7 +3080,7 @@
         ENTERED = 1;
         EXITED = 2;
     }
-    optional State state = 4;
+    optional State state = 4 [(state_field_option).option = EXCLUSIVE];
 }
 
 /*
@@ -3965,7 +3973,7 @@
     // rx time in ms at power level 5
     optional uint64 controller_rx_time_millis = 9;
     // product of current(mA), voltage(V) and time(ms)
-    optional uint64 energy_used = 10;
+    optional uint64 energy_used = 10 [deprecated=true];
 }
 
 /**
@@ -4091,9 +4099,53 @@
     // Provided by ActivityManagerService or read from /proc/PID/cmdline.
     optional string process_name = 2;
 
+    // Deprecated: use rss_high_water_mark_in_kilobytes instead. This field is
+    // computed by converting kilobytes to bytes.
+    optional int64 rss_high_water_mark_in_bytes = 3 [deprecated = true];
+
     // RSS high-water mark. Peak RSS usage of the process. Read from the VmHWM field in
     // /proc/PID/status.
-    optional int64 rss_high_water_mark_in_bytes = 3;
+    optional int32 rss_high_water_mark_in_kilobytes = 4;
+}
+
+/*
+ * Logs the memory stats for a process.
+ *
+ * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService)
+ * and for selected native processes.
+ */
+message ProcessMemorySnapshot {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The process name.
+    // Usually package name or process cmdline.
+    // Provided by ActivityManagerService or read from /proc/PID/cmdline.
+    optional string process_name = 2;
+
+    // The pid of the process.
+    // Allows to disambiguate instances of the process.
+    optional int32 pid = 3;
+
+    // The current OOM score adjustment value.
+    // Read from ProcessRecord for managed processes.
+    // Placeholder -1001 (OOM_SCORE_ADJ_MIN - 1, outside of allowed range) for native ones.
+    optional int32 oom_score_adj = 4;
+
+    // The current RSS of the process.
+    // VmRSS from /proc/pid/status.
+    optional int32 rss_in_kilobytes = 5;
+
+    // The current anon RSS of the process.
+    // RssAnon from /proc/pid/status.
+    optional int32 anon_rss_in_kilobytes = 6;
+
+    // The current swap size of the process.
+    // VmSwap from /proc/pid/status.
+    optional int32 swap_in_kilobytes = 7;
+
+    // The sum of rss_in_kilobytes and swap_in_kilobytes.
+    optional int32 anon_rss_and_swap_in_kilobytes = 8;
 }
 
 /*
@@ -5937,7 +5989,8 @@
     optional bool is_ongoing = 10;
 
     // Whether the bubble is produced by an app running in foreground.
-    optional bool is_foreground = 11;
+    // This is deprecated and the value should be ignored.
+    optional bool is_foreground = 11 [deprecated = true];
 }
 
 /**
@@ -7087,3 +7140,138 @@
     // The number of reboot of the device during a successful update.
     optional int32 reboot_count = 7;
 }
+
+/**
+ * Global display pipeline metrics reported by SurfaceFlinger.
+ * Pulled from:
+ *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
+ */
+message SurfaceflingerStatsGlobalInfo {
+    // Total number of frames presented during the tracing period
+    optional int64 total_frames = 1;
+    // Total number of frames missed
+    optional int64 missed_frames = 2;
+    // Total number of frames that fell back to client composition
+    optional int64 client_composition_frames = 3;
+    // Total time the display was turned on
+    optional int64 display_on_millis = 4;
+    // Total time that was spent performing animations.
+    // This is derived from the present-to-present layer histogram
+    optional int64 animation_millis = 5;
+}
+
+/**
+ * Per-layer display pipeline metrics reported by SurfaceFlinger.
+ * The number of layers uploaded will be restricted due to size limitations.
+ * Pulled from:
+ *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
+ */
+message SurfaceflingerStatsLayerInfo {
+    // The layer for this set of metrics
+    // For now we can infer that the package name is included in the layer
+    // name.
+    optional string layer_name = 1;
+    // Total number of frames presented
+    optional int64 total_frames = 2;
+    // Total number of dropped frames while latching a buffer for this layer.
+    optional int64 dropped_frames = 3;
+    // Set of timings measured between successive presentation timestamps.
+    optional FrameTimingHistogram present_to_present = 4
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when an app queued a buffer for
+    // presentation, until the buffer was actually presented to the
+    // display.
+    optional FrameTimingHistogram post_to_present = 5
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when a buffer is ready to be presented,
+    // until the buffer was actually presented to the display.
+    optional FrameTimingHistogram acquire_to_present = 6
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when a buffer was latched by
+    // SurfaceFlinger, until the buffer was presented to the display
+    optional FrameTimingHistogram latch_to_present = 7
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from the desired presentation to the actual
+    // presentation time
+    optional FrameTimingHistogram desired_to_present = 8
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Set of timings measured from when an app queued a buffer for
+    // presentation, until the buffer was ready to be presented.
+    optional FrameTimingHistogram post_to_acquire = 9
+        [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
+/**
+ * Histogram of frame counts bucketed by time in milliseconds.
+ * Because of size limitations, we hard-cap the number of buckets, with
+ * buckets for corresponding to larger milliseconds being less precise.
+ */
+message FrameTimingHistogram {
+    // Timings in milliseconds that describes a set of histogram buckets
+    repeated int32 time_millis_buckets = 1;
+    // Number of frames that match to each time_millis, i.e. the bucket
+    // contents
+    // It's required that len(time_millis) == len(frame_count)
+    repeated int64 frame_counts = 2;
+}
+
+/**
+ * Information about camera facing and API level usage.
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java
+ */
+message CameraActionEvent {
+    // Camera session duration
+    optional int64 duration = 1;
+
+    // Camera API level used
+    optional int32 api_level = 2;
+
+    // Name of client package
+    optional string package_name = 3;
+
+    // Camera facing
+    enum Facing {
+        UNKNOWN = 0;
+        BACK = 1;
+        FRONT = 2;
+        EXTERNAL = 3;
+    }
+    optional Facing facing = 4;
+}
+
+/**
+ * Logs when a compatibility change is affecting an app.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/android/app/AppCompatCallbacks.java and
+ *   frameworks/base/services/core/java/com/android/server/compat/PlatformCompat.java
+ */
+message AppCompatibilityChangeReported {
+    // The UID of the app being affected by the compatibilty change.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The ID of the change affecting the app.
+    optional int64 change_id = 2;
+
+    enum State {
+        UNKNOWN_STATE = 0;
+        ENABLED = 1;
+        DISABLED = 2;
+        LOGGED = 3;
+    }
+
+    // The state of the change - if logged from gating whether it was enabled or disabled, or just
+    // logged otherwise.
+    optional State state = 3;
+
+    enum Source {
+        UNKNOWN_SOURCE = 0;
+        APP_PROCESS = 1;
+        SYSTEM_SERVER = 2;
+    }
+
+    // Where it was logged from.
+    optional Source source = 4;
+
+}
diff --git a/cmds/statsd/src/condition/StateConditionTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp
new file mode 100644
index 0000000..7f3eedd
--- /dev/null
+++ b/cmds/statsd/src/condition/StateConditionTracker.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateConditionTracker.h"
+#include "guardrail/StatsdStats.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+using std::unordered_set;
+using std::vector;
+
+StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
+                           const SimplePredicate& simplePredicate,
+                           const unordered_map<int64_t, int>& trackerNameIndexMap,
+                           const vector<Matcher> primaryKeys)
+    : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) {
+    if (simplePredicate.has_start()) {
+        auto pair = trackerNameIndexMap.find(simplePredicate.start());
+        if (pair == trackerNameIndexMap.end()) {
+            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
+            return;
+        }
+        mStartLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStartLogMatcherIndex);
+    } else {
+        ALOGW("Condition %lld must have a start matcher", (long long)id);
+        return;
+    }
+
+    if (simplePredicate.has_dimensions()) {
+        translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
+        if (mOutputDimensions.size() > 0) {
+            mSliced = true;
+            mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
+        } else {
+            ALOGW("Condition %lld has invalid dimensions", (long long)id);
+            return;
+        }
+    } else {
+        ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id);
+        return;
+    }
+
+    if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
+        mInitialValue = ConditionState::kFalse;
+    } else {
+        mInitialValue = ConditionState::kUnknown;
+    }
+
+    mNonSlicedConditionState = mInitialValue;
+    mInitialized = true;
+}
+
+StateConditionTracker::~StateConditionTracker() {
+    VLOG("~StateConditionTracker()");
+}
+
+bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig,
+                        const vector<sp<ConditionTracker>>& allConditionTrackers,
+                        const unordered_map<int64_t, int>& conditionIdIndexMap,
+                        vector<bool>& stack) {
+    return mInitialized;
+}
+
+void StateConditionTracker::dumpState() {
+    VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId);
+    for (const auto& value : mSlicedState) {
+        VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
+    }
+    VLOG("Last Changed to True: ");
+    for (const auto& value : mLastChangedToTrueDimensions) {
+        VLOG("%s", value.toString().c_str());
+    }
+    VLOG("Last Changed to False: ");
+    for (const auto& value : mLastChangedToFalseDimensions) {
+        VLOG("%s", value.toString().c_str());
+    }
+}
+
+bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+    if (mSlicedState.find(newKey) != mSlicedState.end()) {
+        // if the condition is not sliced or the key is not new, we are good!
+        return false;
+    }
+    // 1. Report the tuple count if the tuple count > soft limit
+    if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        size_t newTupleCount = mSlicedState.size() + 1;
+        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
+        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
+        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            ALOGE("Predicate %lld dropping data for dimension key %s",
+                (long long)mConditionId, newKey.toString().c_str());
+            return true;
+        }
+    }
+    return false;
+}
+
+void StateConditionTracker::evaluateCondition(const LogEvent& event,
+                                     const vector<MatchingState>& eventMatcherValues,
+                                     const vector<sp<ConditionTracker>>& mAllConditions,
+                                     vector<ConditionState>& conditionCache,
+                                     vector<bool>& conditionChangedCache) {
+    mLastChangedToTrueDimensions.clear();
+    mLastChangedToFalseDimensions.clear();
+    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+        // it has been evaluated.
+        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
+        return;
+    }
+
+    if (mStartLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) {
+        conditionCache[mIndex] =
+                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+        conditionChangedCache[mIndex] = false;
+        return;
+    }
+
+    VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str());
+
+    // Primary key can exclusive fields must be simple fields. so there won't be more than
+    // one keys matched.
+    HashableDimensionKey primaryKey;
+    HashableDimensionKey state;
+    if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) ||
+        !filterValues(mOutputDimensions, event.getValues(), &state)) {
+        ALOGE("Failed to filter fields in the event?? panic now!");
+        conditionCache[mIndex] =
+                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+        conditionChangedCache[mIndex] = false;
+        return;
+    }
+    hitGuardRail(primaryKey);
+
+    VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
+
+    auto it = mSlicedState.find(primaryKey);
+    if (it == mSlicedState.end()) {
+        mSlicedState[primaryKey] = state;
+        conditionCache[mIndex] = ConditionState::kTrue;
+        mLastChangedToTrueDimensions.insert(state);
+        conditionChangedCache[mIndex] = true;
+    } else if (!(it->second == state)) {
+        mLastChangedToFalseDimensions.insert(it->second);
+        mLastChangedToTrueDimensions.insert(state);
+        mSlicedState[primaryKey] = state;
+        conditionCache[mIndex] = ConditionState::kTrue;
+        conditionChangedCache[mIndex] = true;
+    } else {
+        conditionCache[mIndex] = ConditionState::kTrue;
+        conditionChangedCache[mIndex] = false;
+    }
+
+    if (DEBUG) {
+        dumpState();
+    }
+    return;
+}
+
+void StateConditionTracker::isConditionMet(
+        const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
+        const bool isPartialLink,
+        vector<ConditionState>& conditionCache) const {
+    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+        // it has been evaluated.
+        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
+        return;
+    }
+
+    const auto pair = conditionParameters.find(mConditionId);
+    if (pair == conditionParameters.end()) {
+        if (mSlicedState.size() > 0) {
+            conditionCache[mIndex] = ConditionState::kTrue;
+        } else {
+            conditionCache[mIndex] = ConditionState::kUnknown;
+        }
+        return;
+    }
+
+    const auto& primaryKey = pair->second;
+    conditionCache[mIndex] = mInitialValue;
+    auto it = mSlicedState.find(primaryKey);
+    if (it != mSlicedState.end()) {
+        conditionCache[mIndex] = ConditionState::kTrue;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/StateConditionTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h
new file mode 100644
index 0000000..0efe1fb
--- /dev/null
+++ b/cmds/statsd/src/condition/StateConditionTracker.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "ConditionTracker.h"
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateConditionTracker : public virtual ConditionTracker {
+public:
+    StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
+                 const SimplePredicate& simplePredicate,
+                 const std::unordered_map<int64_t, int>& trackerNameIndexMap,
+                 const vector<Matcher> primaryKeys);
+
+    ~StateConditionTracker();
+
+    bool init(const std::vector<Predicate>& allConditionConfig,
+              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
+              std::vector<bool>& stack) override;
+
+    void evaluateCondition(const LogEvent& event,
+                           const std::vector<MatchingState>& eventMatcherValues,
+                           const std::vector<sp<ConditionTracker>>& mAllConditions,
+                           std::vector<ConditionState>& conditionCache,
+                           std::vector<bool>& changedCache) override;
+
+    /**
+     * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics
+     * must take the entire dimension fields from StateConditionTracker. This is to make implementation
+     * simple and efficient.
+     *
+     * For example: wakelock duration by uid process states:
+     *              dimension in condition must be {uid, process state}.
+     */
+    void isConditionMet(const ConditionKey& conditionParameters,
+                        const std::vector<sp<ConditionTracker>>& allConditions,
+                        const bool isPartialLink,
+                        std::vector<ConditionState>& conditionCache) const override;
+
+    virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
+            const std::vector<sp<ConditionTracker>>& allConditions) const {
+        return &mLastChangedToTrueDimensions;
+    }
+
+    virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
+            const std::vector<sp<ConditionTracker>>& allConditions) const {
+        return &mLastChangedToFalseDimensions;
+    }
+
+    bool IsChangedDimensionTrackable() const  override { return true; }
+
+    bool IsSimpleCondition() const  override { return true; }
+
+    bool equalOutputDimensions(
+        const std::vector<sp<ConditionTracker>>& allConditions,
+        const vector<Matcher>& dimensions) const override {
+            return equalDimensions(mOutputDimensions, dimensions);
+    }
+
+    void getTrueSlicedDimensions(
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            std::set<HashableDimensionKey>* dimensions) const override {
+        for (const auto& itr : mSlicedState) {
+            dimensions->insert(itr.second);
+        }
+    }
+
+private:
+    const ConfigKey mConfigKey;
+
+    // The index of the LogEventMatcher which defines the start.
+    int mStartLogMatcherIndex;
+
+    std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
+    std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
+
+    std::vector<Matcher> mOutputDimensions;
+    std::vector<Matcher> mPrimaryKeys;
+
+    ConditionState mInitialValue;
+
+    int mDimensionTag;
+
+    void dumpState();
+
+    bool hitGuardRail(const HashableDimensionKey& newKey);
+
+    // maps from [primary_key] to [primary_key, exclusive_state].
+    std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
+
+    FRIEND_TEST(StateConditionTrackerTest, TestStateChange);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp
deleted file mode 100644
index 18c7178..0000000
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define DEBUG false  // STOPSHIP if true
-#include "Log.h"
-
-#include "StateTracker.h"
-#include "guardrail/StatsdStats.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-using std::string;
-using std::unordered_set;
-using std::vector;
-
-StateTracker::StateTracker(const ConfigKey& key, const int64_t& id, const int index,
-                           const SimplePredicate& simplePredicate,
-                           const unordered_map<int64_t, int>& trackerNameIndexMap,
-                           const vector<Matcher> primaryKeys)
-    : ConditionTracker(id, index), mConfigKey(key), mPrimaryKeys(primaryKeys) {
-    if (simplePredicate.has_start()) {
-        auto pair = trackerNameIndexMap.find(simplePredicate.start());
-        if (pair == trackerNameIndexMap.end()) {
-            ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
-            return;
-        }
-        mStartLogMatcherIndex = pair->second;
-        mTrackerIndex.insert(mStartLogMatcherIndex);
-    } else {
-        ALOGW("Condition %lld must have a start matcher", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.has_dimensions()) {
-        translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
-        if (mOutputDimensions.size() > 0) {
-            mSliced = true;
-            mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
-        } else {
-            ALOGW("Condition %lld has invalid dimensions", (long long)id);
-            return;
-        }
-    } else {
-        ALOGW("Condition %lld being a state tracker, but has no dimension", (long long)id);
-        return;
-    }
-
-    if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
-        mInitialValue = ConditionState::kFalse;
-    } else {
-        mInitialValue = ConditionState::kUnknown;
-    }
-
-    mNonSlicedConditionState = mInitialValue;
-    mInitialized = true;
-}
-
-StateTracker::~StateTracker() {
-    VLOG("~StateTracker()");
-}
-
-bool StateTracker::init(const vector<Predicate>& allConditionConfig,
-                        const vector<sp<ConditionTracker>>& allConditionTrackers,
-                        const unordered_map<int64_t, int>& conditionIdIndexMap,
-                        vector<bool>& stack) {
-    return mInitialized;
-}
-
-void StateTracker::dumpState() {
-    VLOG("StateTracker %lld DUMP:", (long long)mConditionId);
-    for (const auto& value : mSlicedState) {
-        VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
-    }
-    VLOG("Last Changed to True: ");
-    for (const auto& value : mLastChangedToTrueDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-    VLOG("Last Changed to False: ");
-    for (const auto& value : mLastChangedToFalseDimensions) {
-        VLOG("%s", value.toString().c_str());
-    }
-}
-
-bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) {
-    if (mSlicedState.find(newKey) != mSlicedState.end()) {
-        // if the condition is not sliced or the key is not new, we are good!
-        return false;
-    }
-    // 1. Report the tuple count if the tuple count > soft limit
-    if (mSlicedState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-        size_t newTupleCount = mSlicedState.size() + 1;
-        StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
-        // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-            ALOGE("Predicate %lld dropping data for dimension key %s",
-                (long long)mConditionId, newKey.toString().c_str());
-            return true;
-        }
-    }
-    return false;
-}
-
-void StateTracker::evaluateCondition(const LogEvent& event,
-                                     const vector<MatchingState>& eventMatcherValues,
-                                     const vector<sp<ConditionTracker>>& mAllConditions,
-                                     vector<ConditionState>& conditionCache,
-                                     vector<bool>& conditionChangedCache) {
-    mLastChangedToTrueDimensions.clear();
-    mLastChangedToFalseDimensions.clear();
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    if (mStartLogMatcherIndex >= 0 &&
-        eventMatcherValues[mStartLogMatcherIndex] != MatchingState::kMatched) {
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-
-    VLOG("StateTracker evaluate event %s", event.ToString().c_str());
-
-    // Primary key can exclusive fields must be simple fields. so there won't be more than
-    // one keys matched.
-    HashableDimensionKey primaryKey;
-    HashableDimensionKey state;
-    if ((mPrimaryKeys.size() > 0 && !filterValues(mPrimaryKeys, event.getValues(), &primaryKey)) ||
-        !filterValues(mOutputDimensions, event.getValues(), &state)) {
-        ALOGE("Failed to filter fields in the event?? panic now!");
-        conditionCache[mIndex] =
-                mSlicedState.size() > 0 ? ConditionState::kTrue : ConditionState::kFalse;
-        conditionChangedCache[mIndex] = false;
-        return;
-    }
-    hitGuardRail(primaryKey);
-
-    VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
-
-    auto it = mSlicedState.find(primaryKey);
-    if (it == mSlicedState.end()) {
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        mLastChangedToTrueDimensions.insert(state);
-        conditionChangedCache[mIndex] = true;
-    } else if (!(it->second == state)) {
-        mLastChangedToFalseDimensions.insert(it->second);
-        mLastChangedToTrueDimensions.insert(state);
-        mSlicedState[primaryKey] = state;
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = true;
-    } else {
-        conditionCache[mIndex] = ConditionState::kTrue;
-        conditionChangedCache[mIndex] = false;
-    }
-
-    if (DEBUG) {
-        dumpState();
-    }
-    return;
-}
-
-void StateTracker::isConditionMet(
-        const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
-        const bool isPartialLink,
-        vector<ConditionState>& conditionCache) const {
-    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
-        // it has been evaluated.
-        VLOG("Yes, already evaluated, %lld %d", (long long)mConditionId, conditionCache[mIndex]);
-        return;
-    }
-
-    const auto pair = conditionParameters.find(mConditionId);
-    if (pair == conditionParameters.end()) {
-        if (mSlicedState.size() > 0) {
-            conditionCache[mIndex] = ConditionState::kTrue;
-        } else {
-            conditionCache[mIndex] = ConditionState::kUnknown;
-        }
-        return;
-    }
-
-    const auto& primaryKey = pair->second;
-    conditionCache[mIndex] = mInitialValue;
-    auto it = mSlicedState.find(primaryKey);
-    if (it != mSlicedState.end()) {
-        conditionCache[mIndex] = ConditionState::kTrue;
-    }
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h
deleted file mode 100644
index 5ae4441..0000000
--- a/cmds/statsd/src/condition/StateTracker.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-#include "ConditionTracker.h"
-#include "config/ConfigKey.h"
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
-#include "stats_util.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class StateTracker : public virtual ConditionTracker {
-public:
-    StateTracker(const ConfigKey& key, const int64_t& id, const int index,
-                 const SimplePredicate& simplePredicate,
-                 const std::unordered_map<int64_t, int>& trackerNameIndexMap,
-                 const vector<Matcher> primaryKeys);
-
-    ~StateTracker();
-
-    bool init(const std::vector<Predicate>& allConditionConfig,
-              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-              const std::unordered_map<int64_t, int>& conditionIdIndexMap,
-              std::vector<bool>& stack) override;
-
-    void evaluateCondition(const LogEvent& event,
-                           const std::vector<MatchingState>& eventMatcherValues,
-                           const std::vector<sp<ConditionTracker>>& mAllConditions,
-                           std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
-
-    /**
-     * Note: dimensionFields will be ignored in StateTracker, because we demand metrics
-     * must take the entire dimension fields from StateTracker. This is to make implementation
-     * simple and efficient.
-     *
-     * For example: wakelock duration by uid process states:
-     *              dimension in condition must be {uid, process state}.
-     */
-    void isConditionMet(const ConditionKey& conditionParameters,
-                        const std::vector<sp<ConditionTracker>>& allConditions,
-                        const bool isPartialLink,
-                        std::vector<ConditionState>& conditionCache) const override;
-
-    virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToTrueDimensions;
-    }
-
-    virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions) const {
-        return &mLastChangedToFalseDimensions;
-    }
-
-    bool IsChangedDimensionTrackable() const  override { return true; }
-
-    bool IsSimpleCondition() const  override { return true; }
-
-    bool equalOutputDimensions(
-        const std::vector<sp<ConditionTracker>>& allConditions,
-        const vector<Matcher>& dimensions) const override {
-            return equalDimensions(mOutputDimensions, dimensions);
-    }
-
-    void getTrueSlicedDimensions(
-            const std::vector<sp<ConditionTracker>>& allConditions,
-            std::set<HashableDimensionKey>* dimensions) const override {
-        for (const auto& itr : mSlicedState) {
-            dimensions->insert(itr.second);
-        }
-    }
-
-private:
-    const ConfigKey mConfigKey;
-
-    // The index of the LogEventMatcher which defines the start.
-    int mStartLogMatcherIndex;
-
-    std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
-    std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
-
-    std::vector<Matcher> mOutputDimensions;
-    std::vector<Matcher> mPrimaryKeys;
-
-    ConditionState mInitialValue;
-
-    int mDimensionTag;
-
-    void dumpState();
-
-    bool hitGuardRail(const HashableDimensionKey& newKey);
-
-    // maps from [primary_key] to [primary_key, exclusive_state].
-    std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
-
-    FRIEND_TEST(StateTrackerTest, TestStateChange);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 475f18a..f69e2d0 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -153,6 +153,9 @@
          {.additiveFields = {3},
           .puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+        // process_memory_snapshot
+        {android::util::PROCESS_MEMORY_SNAPSHOT,
+         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
         // system_ion_heap_size
         {android::util::SYSTEM_ION_HEAP_SIZE,
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index 68082c2..42132ee 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -78,7 +78,7 @@
     ps->giveThreadPoolName();
     IPCThreadState::self()->disableBackgroundScheduling(true);
 
-    ::android::hardware::configureRpcThreadpool(1 /*threads*/, false /*willJoin*/);
+    ::android::hardware::configureRpcThreadpool(4 /*threads*/, false /*willJoin*/);
 
     std::shared_ptr<LogEventQueue> eventQueue =
             std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 10ac4a1..476fae3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -358,9 +358,10 @@
 
 bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
-    if (simpleMatcher.field_value_matcher_size() <= 0) {
-        return event.GetTagId() == simpleMatcher.atom_id();
+    if (event.GetTagId() != simpleMatcher.atom_id()) {
+        return false;
     }
+
     for (const auto& matcher : simpleMatcher.field_value_matcher()) {
         if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
             return false;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 94f833b..fdbdc83 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -83,9 +83,9 @@
           mCurrentBucketStartTimeNs(timeBaseNs),
           mCurrentBucketNum(0),
           mCondition(initialCondition(conditionIndex)),
+          mConditionTrackerIndex(conditionIndex),
           mConditionSliced(false),
           mWizard(wizard),
-          mConditionTrackerIndex(conditionIndex),
           mContainANYPositionInDimensionsInWhat(false),
           mSliceByPositionALL(false),
           mHasLinksToAllConditionDimensionsInTracker(false),
@@ -167,11 +167,6 @@
         return clearPastBucketsLocked(dumpTimeNs);
     }
 
-    void dumpStates(FILE* out, bool verbose) const {
-        std::lock_guard<std::mutex> lock(mMutex);
-        dumpStatesLocked(out, verbose);
-    }
-
     // Returns the memory in bytes currently used to store this metric's data. Does not change
     // state.
     size_t byteSize() const {
@@ -179,34 +174,9 @@
         return byteSizeLocked();
     }
 
-    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
-    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
-                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+    void dumpStates(FILE* out, bool verbose) const {
         std::lock_guard<std::mutex> lock(mMutex);
-        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
-        if (anomalyTracker != nullptr) {
-            mAnomalyTrackers.push_back(anomalyTracker);
-        }
-        return anomalyTracker;
-    }
-
-    int64_t getBuckeSizeInNs() const {
-        std::lock_guard<std::mutex> lock(mMutex);
-        return mBucketSizeNs;
-    }
-
-    // Only needed for unit-testing to override guardrail.
-    void setBucketSize(int64_t bucketSize) {
-        mBucketSizeNs = bucketSize;
-    }
-
-    inline const int64_t& getMetricId() const {
-        return mMetricId;
-    }
-
-    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        loadActiveMetricLocked(activeMetric, currentTimeNs);
+        dumpStatesLocked(out, verbose);
     }
 
     // Let MetricProducer drop in-memory data to save memory.
@@ -218,9 +188,14 @@
         dropDataLocked(dropTimeNs);
     }
 
-    // For test only.
-    inline int64_t getCurrentBucketNum() const {
-        return mCurrentBucketNum;
+    void prepareFirstBucket() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        prepareFirstBucketLocked();
+    }
+
+    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        loadActiveMetricLocked(activeMetric, currentTimeNs);
     }
 
     void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) {
@@ -238,44 +213,41 @@
         return isActiveLocked();
     }
 
+    void flushIfExpire(int64_t elapsedTimestampNs);
+
     void addActivation(int activationTrackerIndex, const ActivationType& activationType,
             int64_t ttl_seconds, int deactivationTrackerIndex = -1);
 
-    void prepareFirstBucket() {
-        std::lock_guard<std::mutex> lock(mMutex);
-        prepareFirstBucketLocked();
-    }
-
-    void flushIfExpire(int64_t elapsedTimestampNs);
-
     void writeActiveMetricToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
-protected:
-    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
-    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
-                                                  const int64_t eventTime) = 0;
-    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
-                                    const bool include_current_partial_bucket,
-                                    const bool erase_data,
-                                    const DumpLatency dumpLatency,
-                                    std::set<string> *str_set,
-                                    android::util::ProtoOutputStream* protoOutput) = 0;
-    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
-    virtual size_t byteSizeLocked() const = 0;
-    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
 
-    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
-
-    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
-    void cancelEventActivationLocked(int deactivationTrackerIndex);
-
-    inline bool isActiveLocked() const {
-        return mIsActive;
+    // Start: getters/setters
+    inline const int64_t& getMetricId() const {
+        return mMetricId;
     }
 
-    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    // For test only.
+    inline int64_t getCurrentBucketNum() const {
+        return mCurrentBucketNum;
+    }
 
-    virtual void prepareFirstBucketLocked() {};
+    int64_t getBucketSizeInNs() const {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mBucketSizeNs;
+    }
+
+    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
+    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
+                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
+        if (anomalyTracker != nullptr) {
+            mAnomalyTrackers.push_back(anomalyTracker);
+        }
+        return anomalyTracker;
+    }
+    // End: getters/setters
+protected:
     /**
      * Flushes the current bucket if the eventTime is after the current bucket's end time. This will
        also flush the current partial bucket in memory.
@@ -283,14 +255,6 @@
     virtual void flushIfNeededLocked(const int64_t& eventTime){};
 
     /**
-     * Flushes all the data including the current partial bucket.
-     */
-    virtual void flushLocked(const int64_t& eventTimeNs) {
-        flushIfNeededLocked(eventTimeNs);
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-    };
-
-    /**
      * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
      * we need to be able to flush the current buckets on demand (ie, end the current bucket and
      * start new bucket). If this function is called when eventTimeNs is greater than the current
@@ -303,62 +267,13 @@
     virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                           const int64_t& nextBucketStartTimeNs) {};
 
-    virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
-        if (!mIsActive) {
-            flushLocked(eventTimeNs);
-        }
-    }
-
-    // Convenience to compute the current bucket's end time, which is always aligned with the
-    // start time of the metric.
-    int64_t getCurrentBucketEndTimeNs() const {
-        return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
-    }
-
-    int64_t getBucketNumFromEndTimeNs(const int64_t endNs) {
-        return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
-    }
-
-    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
-
-    const int64_t mMetricId;
-
-    const ConfigKey mConfigKey;
-
-    // The time when this metric producer was first created. The end time for the current bucket
-    // can be computed from this based on mCurrentBucketNum.
-    int64_t mTimeBaseNs;
-
-    // Start time may not be aligned with the start of statsd if there is an app upgrade in the
-    // middle of a bucket.
-    int64_t mCurrentBucketStartTimeNs;
-
-    // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
-    // report.
-    int64_t mCurrentBucketNum;
-
-    int64_t mBucketSizeNs;
-
-    ConditionState mCondition;
-
-    bool mConditionSliced;
-
-    sp<ConditionWizard> mWizard;
-
-    int mConditionTrackerIndex;
-
-    vector<Matcher> mDimensionsInWhat;       // The dimensions_in_what defined in statsd_config
-
-    bool mContainANYPositionInDimensionsInWhat;
-    bool mSliceByPositionALL;
-
-    // True iff the metric to condition links cover all dimension fields in the condition tracker.
-    // This field is always false for combinational condition trackers.
-    bool mHasLinksToAllConditionDimensionsInTracker;
-
-    std::vector<Metric2Condition> mMetric2ConditionLinks;
-
-    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+    /**
+     * Flushes all the data including the current partial bucket.
+     */
+    virtual void flushLocked(const int64_t& eventTimeNs) {
+        flushIfNeededLocked(eventTimeNs);
+        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
 
     /*
      * Individual metrics can implement their own business logic here. All pre-processing is done.
@@ -382,6 +297,85 @@
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
+    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
+                                                  const int64_t eventTime) = 0;
+    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
+                                    const bool include_current_partial_bucket,
+                                    const bool erase_data,
+                                    const DumpLatency dumpLatency,
+                                    std::set<string> *str_set,
+                                    android::util::ProtoOutputStream* protoOutput) = 0;
+    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
+    virtual size_t byteSizeLocked() const = 0;
+    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
+    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
+    virtual void prepareFirstBucketLocked() {};
+    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
+    void cancelEventActivationLocked(int deactivationTrackerIndex);
+
+    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
+
+    virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
+        if (!mIsActive) {
+            flushLocked(eventTimeNs);
+        }
+    }
+
+    inline bool isActiveLocked() const {
+        return mIsActive;
+    }
+
+    // Convenience to compute the current bucket's end time, which is always aligned with the
+    // start time of the metric.
+    int64_t getCurrentBucketEndTimeNs() const {
+        return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+    }
+
+    int64_t getBucketNumFromEndTimeNs(const int64_t endNs) {
+        return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
+    }
+
+    const int64_t mMetricId;
+
+    const ConfigKey mConfigKey;
+
+    // The time when this metric producer was first created. The end time for the current bucket
+    // can be computed from this based on mCurrentBucketNum.
+    int64_t mTimeBaseNs;
+
+    // Start time may not be aligned with the start of statsd if there is an app upgrade in the
+    // middle of a bucket.
+    int64_t mCurrentBucketStartTimeNs;
+
+    // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
+    // report.
+    int64_t mCurrentBucketNum;
+
+    int64_t mBucketSizeNs;
+
+    ConditionState mCondition;
+
+    int mConditionTrackerIndex;
+
+    bool mConditionSliced;
+
+    sp<ConditionWizard> mWizard;
+
+    bool mContainANYPositionInDimensionsInWhat;
+
+    bool mSliceByPositionALL;
+
+    vector<Matcher> mDimensionsInWhat;  // The dimensions_in_what defined in statsd_config
+
+    // True iff the metric to condition links cover all dimension fields in the condition tracker.
+    // This field is always false for combinational condition trackers.
+    bool mHasLinksToAllConditionDimensionsInTracker;
+
+    std::vector<Metric2Condition> mMetric2ConditionLinks;
+
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
 
     mutable std::mutex mMutex;
 
@@ -397,6 +391,7 @@
         ActivationState state;
         const ActivationType activationType;
     };
+
     // When the metric producer has multiple activations, these activations are ORed to determine
     // whether the metric producer is ready to generate metrics.
     std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index dd32c08..40484f4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -21,7 +21,7 @@
 
 #include "../condition/CombinationConditionTracker.h"
 #include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateTracker.h"
+#include "../condition/StateConditionTracker.h"
 #include "../external/StatsPullerManager.h"
 #include "../matchers/CombinationLogMatchingTracker.h"
 #include "../matchers/SimpleLogMatchingTracker.h"
@@ -35,6 +35,8 @@
 #include "stats_util.h"
 #include "statslog.h"
 
+#include <inttypes.h>
+
 using std::set;
 using std::string;
 using std::unordered_map;
@@ -182,13 +184,13 @@
 }
 
 /**
- * A StateTracker is built from a SimplePredicate which has only "start", and no "stop"
+ * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop"
  * or "stop_all". The start must be an atom matcher that matches a state atom. It must
  * have dimension, the dimension must be the state atom's primary fields plus exclusive state
- * field. For example, the StateTracker is used in tracking UidProcessState and ScreenState.
+ * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState.
  *
  */
-bool isStateTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
+bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
     // 1. must not have "stop". must have "dimension"
     if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) {
         auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(
@@ -240,8 +242,8 @@
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
                 vector<Matcher> primaryKeys;
-                if (isStateTracker(condition.simple_predicate(), &primaryKeys)) {
-                    allConditionTrackers.push_back(new StateTracker(key, condition.id(), index,
+                if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) {
+                    allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index,
                                                                     condition.simple_predicate(),
                                                                     logTrackerMap, primaryKeys));
                 } else {
@@ -593,7 +595,7 @@
     for (int i = 0; i < config.no_report_metric_size(); ++i) {
         const auto no_report_metric = config.no_report_metric(i);
         if (metricMap.find(no_report_metric) == metricMap.end()) {
-            ALOGW("no_report_metric %lld not exist", no_report_metric);
+            ALOGW("no_report_metric %" PRId64 " not exist", no_report_metric);
             return false;
         }
         noReportMetricIds.insert(no_report_metric);
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 028231f..3704969 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -113,7 +113,7 @@
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds);
 
-bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
+bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
new file mode 100644
index 0000000..a31690a
--- /dev/null
+++ b/cmds/statsd/src/state/StateListener.h
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateListener : public virtual RefBase {
+public:
+    StateListener(){};
+
+    virtual ~StateListener(){};
+
+    /**
+     * Interface for handling a state change.
+     *
+     * The old and new state values map to the original state values.
+     * StateTrackers only track the original state values and are unaware
+     * of higher-level state groups. MetricProducers hold information on
+     * state groups and are responsible for mapping original state values to
+     * the correct state group.
+     *
+     * [atomId]: The id of the state atom
+     * [primaryKey]: The primary field values of the state atom
+     * [oldState]: Previous state value before state change
+     * [newState]: Current state value after state change
+     */
+    virtual void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                                int newState) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
new file mode 100644
index 0000000..a3059c5
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateManager& StateManager::getInstance() {
+    static StateManager sStateManager;
+    return sStateManager;
+}
+
+void StateManager::onLogEvent(const LogEvent& event) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
+        mStateTrackers[event.GetTagId()]->onLogEvent(event);
+    }
+}
+
+bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    // Check if state tracker already exists
+    if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+        // Create a new state tracker iff atom is a state atom
+        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
+            mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+        } else {
+            ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
+                  stateAtomId);
+            return false;
+        }
+    }
+    mStateTrackers[stateAtomId]->registerListener(listener);
+    return true;
+}
+
+void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // Hold the sp<> until the lock is released so that ~StateTracker() is
+    // not called while the lock is held.
+    sp<StateTracker> toRemove;
+
+    // Unregister listener from correct StateTracker
+    auto it = mStateTrackers.find(stateAtomId);
+    if (it != mStateTrackers.end()) {
+        it->second->unregisterListener(listener);
+
+        // Remove the StateTracker if it has no listeners
+        if (it->second->getListenersCount() == 0) {
+            toRemove = it->second;
+            mStateTrackers.erase(it);
+        }
+    } else {
+        ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
+              stateAtomId);
+    }
+    lock.unlock();
+}
+
+int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+        return mStateTrackers[stateAtomId]->getState(key);
+    }
+
+    return StateTracker::kStateUnknown;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
new file mode 100644
index 0000000..ce60f14
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+#pragma once
+
+//#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+
+#include "state/StateListener.h"
+#include "state/StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateManager : public virtual RefBase {
+public:
+    StateManager(){};
+
+    ~StateManager(){};
+
+    // Returns a pointer to the single, shared StateManager object.
+    static StateManager& getInstance();
+
+    // Notifies the correct StateTracker of an event.
+    void onLogEvent(const LogEvent& event);
+
+    // Returns true if stateAtomId is the id of a state atom and notifies the
+    // correct StateTracker to register the listener. If the correct
+    // StateTracker does not exist, a new StateTracker is created.
+    bool registerListener(int stateAtomId, wp<StateListener> listener);
+
+    // Notifies the correct StateTracker to unregister a listener
+    // and removes the tracker if it no longer has any listeners.
+    void unregisterListener(int stateAtomId, wp<StateListener> listener);
+
+    // Queries the correct StateTracker for the state that is mapped to the given
+    // query key.
+    // If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
+    int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+
+    inline int getStateTrackersCount() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mStateTrackers.size();
+    }
+
+    inline int getListenersCount(int stateAtomId) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+            return mStateTrackers[stateAtomId]->getListenersCount();
+        }
+        return -1;
+    }
+
+private:
+  mutable std::mutex mMutex;
+
+  // Maps state atom ids to StateTrackers
+  std::unordered_map<int, sp<StateTracker>> mStateTrackers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
new file mode 100644
index 0000000..5a91950
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "stats_util.h"
+
+#include "StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateTracker::StateTracker(const int atomId,
+                           const util::StateAtomFieldOptions& stateAtomInfo)
+  : mAtomId(atomId),
+    mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
+    // create matcher for each primary field
+    // TODO(tsaichristine): handle when primary field is first uid in chain
+    for (const auto& primary : stateAtomInfo.primaryFields) {
+        Matcher matcher = getSimpleMatcher(atomId, primary);
+        mPrimaryFields.push_back(matcher);
+    }
+
+    // TODO(tsaichristine): set default state, reset state, and nesting
+}
+
+void StateTracker::onLogEvent(const LogEvent& event) {
+    // parse event for primary field values i.e. primary key
+    HashableDimensionKey primaryKey;
+    if (mPrimaryFields.size() > 0) {
+        if (!filterValues(mPrimaryFields, event.getValues(), &primaryKey) ||
+            primaryKey.getValues().size() != mPrimaryFields.size()) {
+            ALOGE("StateTracker error extracting primary key from log event.");
+            handleReset();
+            return;
+        }
+    } else {
+        // atom has no primary fields
+        primaryKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    // parse event for state value
+    Value state;
+    int32_t stateValue;
+    if (!filterValues(mStateField, event.getValues(), &state) || state.getType() != INT) {
+        ALOGE("StateTracker error extracting state from log event. Type: %d", state.getType());
+        handlePartialReset(primaryKey);
+        return;
+    }
+    stateValue = state.int_value;
+
+    if (stateValue == mResetState) {
+        VLOG("StateTracker Reset state: %s", state.toString().c_str());
+        handleReset();
+    }
+
+    // track and update state
+    int32_t oldState = 0;
+    int32_t newState = 0;
+    updateState(primaryKey, stateValue, &oldState, &newState);
+
+    // notify all listeners if state has changed
+    if (oldState != newState) {
+        VLOG("StateTracker updated state");
+        for (auto listener : mListeners) {
+            auto sListener = listener.promote();  // safe access to wp<>
+            if (sListener != nullptr) {
+                sListener->onStateChanged(mAtomId, primaryKey, oldState, newState);
+            }
+        }
+    } else {
+        VLOG("StateTracker NO updated state");
+    }
+}
+
+void StateTracker::registerListener(wp<StateListener> listener) {
+    mListeners.insert(listener);
+}
+
+void StateTracker::unregisterListener(wp<StateListener> listener) {
+    mListeners.erase(listener);
+}
+
+int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+    if (queryKey.getValues().size() == mPrimaryFields.size()) {
+        auto it = mStateMap.find(queryKey);
+        if (it != mStateMap.end()) {
+            return it->second.state;
+        }
+    } else if (queryKey.getValues().size() > mPrimaryFields.size()) {
+        ALOGE("StateTracker query key size > primary key size is illegal");
+    } else {
+        ALOGE("StateTracker query key size < primary key size is not supported");
+    }
+    return mDefaultState;
+}
+
+void StateTracker::handleReset() {
+    VLOG("StateTracker handle reset");
+    for (const auto pair : mStateMap) {
+        for (auto l : mListeners) {
+            auto sl = l.promote();
+            if (sl != nullptr) {
+                sl->onStateChanged(mAtomId, pair.first, pair.second.state, mDefaultState);
+            }
+        }
+    }
+    mStateMap.clear();
+}
+
+void StateTracker::handlePartialReset(const HashableDimensionKey& primaryKey) {
+    VLOG("StateTracker handle partial reset");
+    if (mStateMap.find(primaryKey) != mStateMap.end()) {
+        mStateMap.erase(primaryKey);
+    }
+}
+
+void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                               int32_t* oldState, int32_t* newState) {
+    // get old state (either current state in map or default state)
+    auto it = mStateMap.find(primaryKey);
+    if (it != mStateMap.end()) {
+        *oldState = it->second.state;
+    } else {
+        *oldState = mDefaultState;
+    }
+
+    // update state map
+    if (eventState == mDefaultState) {
+        // remove (key, state) pair if state returns to default state
+        VLOG("\t StateTracker changed to default state")
+        mStateMap.erase(primaryKey);
+    } else {
+        mStateMap[primaryKey].state = eventState;
+        mStateMap[primaryKey].count = 1;
+    }
+    *newState = eventState;
+
+    // TODO(tsaichristine): support atoms with nested counting
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
new file mode 100644
index 0000000..f22706c
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <statslog.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+
+#include "state/StateListener.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateTracker : public virtual RefBase {
+public:
+    StateTracker(const int atomId, const util::StateAtomFieldOptions& stateAtomInfo);
+
+    virtual ~StateTracker(){};
+
+    // Updates state map and notifies all listeners if a state change occurs.
+    // Checks if a state change has occurred by getting the state value from
+    // the log event and comparing the old and new states.
+    void onLogEvent(const LogEvent& event);
+
+    // Adds new listeners to set of StateListeners. If a listener is already
+    // registered, it is ignored.
+    void registerListener(wp<StateListener> listener);
+
+    void unregisterListener(wp<StateListener> listener);
+
+    // Returns the state value mapped to the given query key.
+    // If the key isn't mapped to a state or the key size doesn't match the
+    // primary key size, the default state is returned.
+    int getState(const HashableDimensionKey& queryKey) const;
+
+    inline int getListenersCount() const {
+        return mListeners.size();
+    }
+
+    const static int kStateUnknown = -1;
+
+private:
+    struct StateValueInfo {
+        int32_t state;  // state value
+        int count;      // nested count (only used for binary states)
+    };
+
+    const int32_t mAtomId;  // id of the state atom being tracked
+
+    Matcher mStateField;  // matches the atom's exclusive state field
+
+    std::vector<Matcher> mPrimaryFields;  // matches the atom's primary fields
+
+    int32_t mDefaultState = kStateUnknown;
+
+    int32_t mResetState;
+
+    // Maps primary key to state value info
+    std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
+
+    // Set of all StateListeners (objects listening for state changes)
+    std::set<wp<StateListener>> mListeners;
+
+    // Reset all state values in map to default state
+    void handleReset();
+
+    // Reset only the state value mapped to primary key to default state
+    void handlePartialReset(const HashableDimensionKey& primaryKey);
+
+    // Update the StateMap based on the received state value.
+    // Store the old and new states.
+    void updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                     int32_t* oldState, int32_t* newState);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 70f0f6f..441d3c8 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -330,6 +330,7 @@
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Tag found in kAtomsWithUidField and has matching uid
+    simpleMatcher->set_atom_id(TAG_ID_2);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     // Tag found in kAtomsWithUidField but has non-matching uid
diff --git a/cmds/statsd/tests/condition/StateConditionTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
new file mode 100644
index 0000000..fbf6efd
--- /dev/null
+++ b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
@@ -0,0 +1,112 @@
+// 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.
+
+#include "src/condition/StateConditionTracker.h"
+#include "tests/statsd_test_util.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <numeric>
+#include <vector>
+
+using std::map;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+namespace android {
+namespace os {
+namespace statsd {
+
+const int kUidProcTag = 27;
+
+SimplePredicate getUidProcStatePredicate() {
+    SimplePredicate simplePredicate;
+    simplePredicate.set_start(StringToId("UidProcState"));
+
+    simplePredicate.mutable_dimensions()->set_field(kUidProcTag);
+    simplePredicate.mutable_dimensions()->add_child()->set_field(1);
+    simplePredicate.mutable_dimensions()->add_child()->set_field(2);
+
+    simplePredicate.set_count_nesting(false);
+    return simplePredicate;
+}
+
+void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) {
+    event->write(uid);
+    event->write(state);
+    event->init();
+}
+
+TEST(StateConditionTrackerTest, TestStateChange) {
+    int uid1 = 111;
+    int uid2 = 222;
+
+    int state1 = 1001;
+    int state2 = 1002;
+    unordered_map<int64_t, int> trackerNameIndexMap;
+    trackerNameIndexMap[StringToId("UidProcState")] = 0;
+    vector<Matcher> primaryFields;
+    primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
+    StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
+                         trackerNameIndexMap, primaryFields);
+
+    LogEvent event(kUidProcTag, 0 /*timestamp*/);
+    makeUidProcStateEvent(uid1, state1, &event);
+
+    vector<MatchingState> matcherState;
+    matcherState.push_back(MatchingState::kMatched);
+    vector<sp<ConditionTracker>> allPredicates;
+    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+    vector<bool> changedCache(1, false);
+
+    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
+    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
+    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    changedCache[0] = false;
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
+    EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size());
+    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
+    EXPECT_FALSE(changedCache[0]);
+
+    LogEvent event2(kUidProcTag, 0 /*timestamp*/);
+    makeUidProcStateEvent(uid1, state2, &event2);
+
+    changedCache[0] = false;
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache);
+    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
+    EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size());
+    EXPECT_TRUE(changedCache[0]);
+
+    LogEvent event3(kUidProcTag, 0 /*timestamp*/);
+    makeUidProcStateEvent(uid2, state1, &event3);
+    changedCache[0] = false;
+    conditionCache[0] = ConditionState::kNotEvaluated;
+    tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache);
+    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
+    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
+    EXPECT_TRUE(changedCache[0]);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/condition/StateTracker_test.cpp b/cmds/statsd/tests/condition/StateTracker_test.cpp
deleted file mode 100644
index 9a66254..0000000
--- a/cmds/statsd/tests/condition/StateTracker_test.cpp
+++ /dev/null
@@ -1,112 +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.
-
-#include "src/condition/StateTracker.h"
-#include "tests/statsd_test_util.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <numeric>
-#include <vector>
-
-using std::map;
-using std::unordered_map;
-using std::vector;
-
-#ifdef __ANDROID__
-namespace android {
-namespace os {
-namespace statsd {
-
-const int kUidProcTag = 27;
-
-SimplePredicate getUidProcStatePredicate() {
-    SimplePredicate simplePredicate;
-    simplePredicate.set_start(StringToId("UidProcState"));
-
-    simplePredicate.mutable_dimensions()->set_field(kUidProcTag);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(1);
-    simplePredicate.mutable_dimensions()->add_child()->set_field(2);
-
-    simplePredicate.set_count_nesting(false);
-    return simplePredicate;
-}
-
-void makeUidProcStateEvent(int32_t uid, int32_t state, LogEvent* event) {
-    event->write(uid);
-    event->write(state);
-    event->init();
-}
-
-TEST(StateTrackerTest, TestStateChange) {
-    int uid1 = 111;
-    int uid2 = 222;
-
-    int state1 = 1001;
-    int state2 = 1002;
-    unordered_map<int64_t, int> trackerNameIndexMap;
-    trackerNameIndexMap[StringToId("UidProcState")] = 0;
-    vector<Matcher> primaryFields;
-    primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
-    StateTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
-                         trackerNameIndexMap, primaryFields);
-
-    LogEvent event(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid1, state1, &event);
-
-    vector<MatchingState> matcherState;
-    matcherState.push_back(MatchingState::kMatched);
-    vector<sp<ConditionTracker>> allPredicates;
-    vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
-
-    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(0ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_FALSE(changedCache[0]);
-
-    LogEvent event2(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid1, state2, &event2);
-
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event2, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(1ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-
-    LogEvent event3(kUidProcTag, 0 /*timestamp*/);
-    makeUidProcStateEvent(uid2, state1, &event3);
-    changedCache[0] = false;
-    conditionCache[0] = ConditionState::kNotEvaluated;
-    tracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache, changedCache);
-    EXPECT_EQ(1ULL, tracker.mLastChangedToTrueDimensions.size());
-    EXPECT_EQ(0ULL, tracker.mLastChangedToFalseDimensions.size());
-    EXPECT_TRUE(changedCache[0]);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
new file mode 100644
index 0000000..c89ffea
--- /dev/null
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -0,0 +1,356 @@
+/*
+ * 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.
+ */
+#include <gtest/gtest.h>
+#include "state/StateManager.h"
+#include "state/StateTracker.h"
+#include "state/StateListener.h"
+
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Mock StateListener class for testing.
+ * Stores primary key and state pairs.
+ */
+class TestStateListener : public virtual StateListener {
+public:
+    TestStateListener(){};
+
+    virtual ~TestStateListener(){};
+
+    struct Update {
+        Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){};
+        HashableDimensionKey mKey;
+        int mState;
+    };
+
+    std::vector<Update> updates;
+
+    void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+                        int newState) {
+        updates.emplace_back(primaryKey, newState);
+    }
+};
+
+// START: build event functions.
+// State with no primary fields - ScreenStateChanged
+std::shared_ptr<LogEvent> buildScreenEvent(int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::SCREEN_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with one primary field - UidProcessStateChanged
+std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::UID_PROCESS_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with multiple primary fields - OverlayStateChanged
+std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write(true);  // using_alert_window
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+// END: build event functions.
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
+}
+// END: get primary key functions
+
+TEST(StateListenerTest, TestStateListenerWeakPointer) {
+    sp<TestStateListener> listener = new TestStateListener();
+    wp<TestStateListener> wListener = listener;
+    listener = nullptr;  // let go of listener
+    EXPECT_TRUE(wListener.promote() == nullptr);
+}
+
+TEST(StateManagerTest, TestStateManagerGetInstance) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager& mgr = StateManager::getInstance();
+
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+}
+
+/**
+ * Test registering listeners to StateTrackers
+ *
+ * - StateManager will create a new StateTracker if it doesn't already exist
+ * and then register the listener to the StateTracker.
+ * - If a listener is already registered to a StateTracker, it is not added again.
+ * - StateTrackers are only created for atoms that are state atoms.
+ */
+TEST(StateTrackerTest, TestRegisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Register listener to non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register already registered listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to non-state atom
+    mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+}
+
+/**
+ * Test unregistering listeners from StateTrackers
+ *
+ * - StateManager will unregister listeners from a StateTracker only if the
+ * StateTracker exists and the listener is registered to the StateTracker.
+ * - Once all listeners are removed from a StateTracker, the StateTracker
+ * is also removed.
+ */
+TEST(StateTrackerTest, TestUnregisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Unregister listener from non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister non-registered listener from existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister second-to-last listener from StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister last listener from StateTracker
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states without primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey);
+    EXPECT_EQ(2, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1002, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getUidProcessKey(1000, &queryKey);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mState);
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged
+ * when there is an error extracting state from log event. Listener is not
+ * updated of state change.
+ */
+TEST(StateTrackerTest, TestStateChangeEventError) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildIncorrectOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(0, listener1->updates.size());
+}
+
+TEST(StateTrackerTest, TestStateQuery) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    sp<TestStateListener> listener3 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+
+    std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  // state value: 1003
+    std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
+            1002,
+            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  // state value: 1000
+    std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event5 =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);  // state value:
+    std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
+    std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+
+    mgr.onLogEvent(*event1);
+    mgr.onLogEvent(*event2);
+    mgr.onLogEvent(*event3);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event6);
+    mgr.onLogEvent(*event7);
+
+    // Query for UidProcessState of uid 1001
+    HashableDimensionKey queryKey1;
+    getUidProcessKey(1001, &queryKey1);
+    EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for UidProcessState of uid 1004 - not in state map
+    HashableDimensionKey queryKey2;
+    getUidProcessKey(1004, &queryKey2);
+    EXPECT_EQ(-1,
+              mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2));  // default state
+
+    // Query for UidProcessState of uid 1001 - after change in state
+    mgr.onLogEvent(*event4);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for ScreenState
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+
+    // Query for OverlayState of uid 1000, package name "package2"
+    HashableDimensionKey queryKey3;
+    getOverlayKey(1000, "package2", &queryKey3);
+    EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 2ac8409..652669c 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43765,3 +43765,1725 @@
 HSPLjava/util/Arrays$ArrayList;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V
 HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+[[F
+[[J
+Landroid/accessibilityservice/IAccessibilityServiceClient$Stub;
+Landroid/accessibilityservice/IAccessibilityServiceClient$Stub$Proxy;
+[Landroid/accounts/Account;
+Landroid/accounts/AccountManager$17;
+Landroid/accounts/AccountManager$3;
+[Landroid/accounts/AuthenticatorDescription;
+Landroid/accounts/IAccountAuthenticator;
+Landroid/accounts/IAccountAuthenticator$Stub;
+Landroid/accounts/IAccountAuthenticator$Stub$Proxy;
+Landroid/accounts/IAccountManagerResponse$Stub$Proxy;
+Landroid/animation/AnimationHandler$2;
+[Landroid/animation/Animator;
+Landroid/animation/BidirectionalTypeConverter;
+[Landroid/animation/Keyframe;
+[Landroid/animation/Keyframe$FloatKeyframe;
+[Landroid/animation/Keyframe$IntKeyframe;
+[Landroid/animation/Keyframe$ObjectKeyframe;
+Landroid/animation/LayoutTransition$3;
+Landroid/animation/PathKeyframes$3;
+Landroid/animation/PathKeyframes$4;
+[Landroid/animation/PropertyValuesHolder;
+Landroid/animation/PropertyValuesHolder$1;
+[Landroid/apex/ApexInfo;
+Landroid/apex/ApexInfo;
+Landroid/app/-$$Lambda$ActivityThread$A4ykhsPb8qV3ffTqpQDklHSMDJ0;
+Landroid/app/-$$Lambda$ActivityThread$Wg40iAoNYFxps_KmrqtgptTB054;
+Landroid/app/-$$Lambda$ActivityTransitionState$yioLR6wQWjZ9DcWK5bibElIbsXc;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$DkVcBvqB32SMHlxw0sWQPh3GL1A;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$HUOLFYs8TiaQIOXcrq6JzjxA6gs;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$Vs6pDL0wjOBTquwNnreWVbPQrn4;
+Landroid/app/-$$Lambda$FragmentTransition$jurn0WXuKw3bRQ_2d5zCWdeZWuI;
+Landroid/app/-$$Lambda$WallpaperManager$Globals$1AcnQUORvPlCjJoNqdxfQT4o4Nw;
+Landroid/app/-$$Lambda$WallpaperManager$Globals$2yG7V1sbMECCnlFTLyjKWKqNoYI;
+Landroid/app/Activity$1;
+Landroid/app/Activity$ManagedCursor;
+Landroid/app/Activity$ManagedDialog;
+Landroid/app/ActivityManager$TaskSnapshot;
+Landroid/app/ActivityTransitionState$1;
+Landroid/app/admin/DevicePolicyCache$EmptyDevicePolicyCache;
+Landroid/app/admin/DevicePolicyEventLogger;
+Landroid/app/admin/IDeviceAdminService;
+Landroid/app/admin/IDeviceAdminService$Stub$Proxy;
+Landroid/app/admin/StartInstallingUpdateCallback$Stub$Proxy;
+Landroid/app/AppCompatCallbacks;
+Landroid/app/ApplicationErrorReport;
+Landroid/app/ApplicationLoaders$CachedClassLoader;
+Landroid/app/ApplicationPackageManager$MoveCallbackDelegate;
+Landroid/app/AppOpsManager$1;
+Landroid/app/AppOpsManager$3;
+[Landroid/app/assist/AssistStructure$ViewNode;
+Landroid/app/AutomaticZenRule;
+[Landroid/app/BackStackState;
+Landroid/app/BackStackState;
+Landroid/app/backup/BlobBackupHelper;
+Landroid/app/backup/IBackupCallback$Stub;
+Landroid/app/backup/IBackupCallback$Stub$Proxy;
+Landroid/app/backup/IBackupManagerMonitor$Stub;
+Landroid/app/backup/IBackupManagerMonitor$Stub$Proxy;
+Landroid/app/backup/IBackupObserver$Stub;
+Landroid/app/backup/IBackupObserver$Stub$Proxy;
+Landroid/app/backup/IFullBackupRestoreObserver$Stub;
+Landroid/app/backup/IFullBackupRestoreObserver$Stub$Proxy;
+Landroid/app/backup/ISelectBackupTransportCallback$Stub;
+Landroid/app/backup/ISelectBackupTransportCallback$Stub$Proxy;
+Landroid/app/DownloadManager$CursorTranslator;
+Landroid/app/EnterTransitionCoordinator;
+Landroid/app/ExitTransitionCoordinator;
+Landroid/app/Fragment$InstantiationException;
+Landroid/app/FragmentManager$FragmentLifecycleCallbacks;
+Landroid/app/FragmentManager$OnBackStackChangedListener;
+Landroid/app/FragmentManagerImpl$2;
+Landroid/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener;
+Landroid/app/FragmentManagerImpl$StartEnterTransitionListener;
+Landroid/app/FragmentManagerNonConfig;
+[Landroid/app/FragmentState;
+Landroid/app/FragmentTransition$2;
+Landroid/app/FragmentTransition$5;
+Landroid/app/IActivityController$Stub;
+Landroid/app/IActivityController$Stub$Proxy;
+Landroid/app/IAlarmCompleteListener$Stub$Proxy;
+Landroid/app/IAlarmListener$Stub$Proxy;
+Landroid/app/IAssistDataReceiver;
+Landroid/app/IAssistDataReceiver$Stub;
+Landroid/app/IAssistDataReceiver$Stub$Proxy;
+Landroid/app/IBackupAgent$Stub$Proxy;
+Landroid/app/IInstantAppResolver;
+Landroid/app/IInstantAppResolver$Stub;
+Landroid/app/IInstantAppResolver$Stub$Proxy;
+Landroid/app/IInstrumentationWatcher$Stub$Proxy;
+Landroid/app/Instrumentation$ActivityGoing;
+Landroid/app/Instrumentation$ActivityMonitor;
+Landroid/app/Instrumentation$ActivityWaiter;
+Landroid/app/IProcessObserver$Stub$Proxy;
+Landroid/app/IRequestFinishCallback$Stub;
+Landroid/app/IRequestFinishCallback$Stub$Proxy;
+Landroid/app/IServiceConnection$Stub$Proxy;
+Landroid/app/IStopUserCallback$Stub;
+Landroid/app/IStopUserCallback$Stub$Proxy;
+Landroid/app/ITransientNotification$Stub$Proxy;
+Landroid/app/IUiAutomationConnection$Stub$Proxy;
+Landroid/app/IUriGrantsManager$Stub$Proxy;
+Landroid/app/job/IJobService$Stub$Proxy;
+[Landroid/app/job/JobInfo$TriggerContentUri;
+Landroid/app/LoadedApk$SplitDependencyLoaderImpl;
+[Landroid/app/LoaderManagerImpl;
+[Landroid/app/Notification$Action;
+Landroid/app/Notification$BubbleMetadata;
+[Landroid/app/NotificationChannel;
+[Landroid/app/NotificationChannelGroup;
+Landroid/app/PackageInstallObserver$1;
+Landroid/app/PendingIntent$FinishedDispatcher;
+[Landroid/app/Person;
+Landroid/app/PictureInPictureParams;
+Landroid/app/prediction/AppPredictionContext;
+Landroid/app/prediction/AppPredictionSessionId;
+Landroid/app/prediction/AppPredictor;
+Landroid/app/prediction/AppTarget;
+Landroid/app/prediction/AppTargetEvent;
+Landroid/app/prediction/AppTargetId;
+Landroid/app/prediction/IPredictionCallback$Stub;
+Landroid/app/prediction/IPredictionCallback$Stub$Proxy;
+Landroid/app/prediction/IPredictionManager$Stub$Proxy;
+Landroid/app/RemoteAction;
+[Landroid/app/RemoteInput;
+Landroid/app/RemoteServiceException;
+Landroid/app/role/-$$Lambda$o94o2jK_ei-IVw-3oY_QJ49zpAA;
+Landroid/app/role/-$$Lambda$RoleControllerManager$9hUe0y0G47wIHKhViIp3z2IRAIk;
+Landroid/app/role/-$$Lambda$RoleControllerManager$Jsb4ev7pHUqel8_lglNSRLiUzpg;
+Landroid/app/role/IOnRoleHoldersChangedListener$Stub$Proxy;
+Landroid/app/role/IRoleController$Stub;
+Landroid/app/SearchableInfo;
+Landroid/app/SearchableInfo$ActionKeyInfo;
+Landroid/app/servertransaction/ActivityConfigurationChangeItem;
+[Landroid/app/slice/SliceItem;
+[Landroid/app/slice/SliceSpec;
+Landroid/app/StatsManager$StatsdDeathRecipient;
+Landroid/app/trust/TrustManager$1;
+Landroid/app/trust/TrustManager$TrustListener;
+Landroid/app/usage/ICacheQuotaService$Stub$Proxy;
+Landroid/app/usage/NetworkStatsManager$CallbackHandler;
+Landroid/app/usage/NetworkStatsManager$UsageCallback;
+Landroid/app/VoiceInteractor;
+Landroid/app/Vr2dDisplayProperties;
+Landroid/app/WallpaperInfo;
+Landroid/app/WallpaperManager$OnColorsChangedListener;
+Landroid/appwidget/AppWidgetProviderInfo;
+Landroid/bluetooth/-$$Lambda$BluetoothAdapter$2$INSd_aND-SGWhhPZUtIqya_Uxw4;
+Landroid/bluetooth/BluetoothA2dpSink;
+Landroid/bluetooth/BluetoothAvrcpController;
+[Landroid/bluetooth/BluetoothCodecConfig;
+Landroid/bluetooth/BluetoothCodecStatus;
+[Landroid/bluetooth/BluetoothDevice;
+Landroid/bluetooth/BluetoothGattService;
+Landroid/bluetooth/BluetoothHeadsetClient;
+Landroid/bluetooth/BluetoothHidDevice$1;
+Landroid/bluetooth/BluetoothHidDeviceAppQosSettings;
+Landroid/bluetooth/BluetoothHidDeviceAppSdpSettings;
+Landroid/bluetooth/BluetoothHidHost$1;
+Landroid/bluetooth/BluetoothMap$1;
+Landroid/bluetooth/BluetoothMapClient;
+Landroid/bluetooth/BluetoothPan$1;
+Landroid/bluetooth/BluetoothPbap$1;
+Landroid/bluetooth/BluetoothPbap$2;
+Landroid/bluetooth/BluetoothPbapClient;
+Landroid/bluetooth/BluetoothProfileConnector$1;
+Landroid/bluetooth/BluetoothProfileConnector$2;
+Landroid/bluetooth/BluetoothSap$1;
+Landroid/bluetooth/IBluetoothGattCallback$Stub;
+Landroid/bluetooth/IBluetoothGattCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothGattServerCallback$Stub;
+Landroid/bluetooth/IBluetoothGattServerCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidDevice$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidDeviceCallback$Stub;
+Landroid/bluetooth/IBluetoothHidDeviceCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidHost$Stub$Proxy;
+Landroid/bluetooth/IBluetoothMap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothMetadataListener$Stub$Proxy;
+Landroid/bluetooth/IBluetoothPan$Stub$Proxy;
+Landroid/bluetooth/IBluetoothPbap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothProfileServiceConnection$Stub$Proxy;
+Landroid/bluetooth/IBluetoothSap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothStateChangeCallback$Stub$Proxy;
+Landroid/bluetooth/le/AdvertiseData;
+Landroid/bluetooth/le/AdvertisingSetParameters;
+Landroid/bluetooth/le/BluetoothLeScanner;
+Landroid/bluetooth/le/IAdvertisingSetCallback$Stub;
+Landroid/bluetooth/le/IAdvertisingSetCallback$Stub$Proxy;
+Landroid/bluetooth/le/IPeriodicAdvertisingCallback$Stub;
+Landroid/bluetooth/le/IPeriodicAdvertisingCallback$Stub$Proxy;
+Landroid/bluetooth/le/IScannerCallback$Stub;
+Landroid/bluetooth/le/PeriodicAdvertisingParameters;
+Landroid/bluetooth/OobData;
+[Landroid/bluetooth/UidTraffic;
+Landroid/companion/AssociationRequest;
+Landroid/companion/ICompanionDeviceManager$Stub$Proxy;
+Landroid/companion/IFindDeviceCallback$Stub;
+Landroid/companion/IFindDeviceCallback$Stub$Proxy;
+Landroid/compat/Compatibility;
+Landroid/content/ClipData;
+Landroid/content/ClipData$Item;
+Landroid/content/ClipDescription;
+Landroid/content/ContentProviderClient$NotRespondingRunnable;
+[Landroid/content/ContentValues;
+[Landroid/content/Intent;
+[Landroid/content/IntentFilter;
+Landroid/content/IntentSender$FinishedDispatcher;
+Landroid/content/IOnPrimaryClipChangedListener$Stub$Proxy;
+Landroid/content/IRestrictionsManager$Stub$Proxy;
+Landroid/content/ISyncAdapter$Stub$Proxy;
+Landroid/content/ISyncStatusObserver$Stub$Proxy;
+Landroid/content/LocusId;
+Landroid/content/om/IOverlayManager$Stub$Proxy;
+Landroid/content/pm/-$$Lambda$ciir_QAmv6RwJro4I58t77dPnxU;
+Landroid/content/pm/-$$Lambda$n3uXeb1v-YRmq_BWTfosEqUUr9g;
+Landroid/content/pm/-$$Lambda$PackageParser$0aobsT7Zf7WVZCqMZ5z2clAuQf4;
+Landroid/content/pm/-$$Lambda$PackageParser$0DZRgzfgaIMpCOhJqjw6PUiU5vw;
+Landroid/content/pm/-$$Lambda$PackageParser$M-9fHqS_eEp1oYkuKJhRHOGUxf8;
+Landroid/content/pm/-$$Lambda$T1UQAuePWRRmVQ1KzTyMAktZUPM;
+Landroid/content/pm/-$$Lambda$zO9HBUVgPeroyDQPLJE-MNMvSqc;
+[Landroid/content/pm/ActivityInfo;
+Landroid/content/pm/BaseParceledListSlice$1;
+[Landroid/content/pm/ConfigurationInfo;
+Landroid/content/pm/dex/DexMetadataHelper;
+Landroid/content/pm/dex/ISnapshotRuntimeProfileCallback$Stub;
+Landroid/content/pm/dex/ISnapshotRuntimeProfileCallback$Stub$Proxy;
+[Landroid/content/pm/FeatureGroupInfo;
+[Landroid/content/pm/FeatureInfo;
+Landroid/content/pm/IDexModuleRegisterCallback$Stub;
+Landroid/content/pm/IDexModuleRegisterCallback$Stub$Proxy;
+Landroid/content/pm/InstantAppIntentFilter;
+Landroid/content/pm/InstantAppResolveInfo;
+Landroid/content/pm/InstantAppResolveInfo$InstantAppDigest;
+[Landroid/content/pm/InstrumentationInfo;
+Landroid/content/pm/IOnAppsChangedListener$Stub$Proxy;
+Landroid/content/pm/IPackageDataObserver$Stub$Proxy;
+Landroid/content/pm/IPackageDeleteObserver$Stub;
+Landroid/content/pm/IPackageDeleteObserver$Stub$Proxy;
+Landroid/content/pm/IPackageDeleteObserver2$Stub;
+Landroid/content/pm/IPackageDeleteObserver2$Stub$Proxy;
+Landroid/content/pm/IPackageInstallerCallback$Stub$Proxy;
+Landroid/content/pm/IPackageMoveObserver$Stub$Proxy;
+Landroid/content/pm/IPackageStatsObserver$Stub$Proxy;
+Landroid/content/pm/KeySet;
+Landroid/content/pm/LauncherActivityInfo;
+Landroid/content/pm/LauncherApps$CallbackMessageHandler;
+[Landroid/content/pm/PackageInfo;
+Landroid/content/pm/PackageParser$Instrumentation;
+[Landroid/content/pm/PathPermission;
+[Landroid/content/pm/PermissionInfo;
+[Landroid/content/pm/ProviderInfo;
+[Landroid/content/pm/ServiceInfo;
+[Landroid/content/pm/Signature;
+Landroid/content/pm/SigningInfo;
+Landroid/content/pm/split/SplitAssetDependencyLoader;
+Landroid/content/pm/split/SplitDependencyLoader;
+Landroid/content/pm/SuspendDialogInfo;
+[Landroid/content/pm/VerifierInfo;
+Landroid/content/res/AssetFileDescriptor$AutoCloseInputStream;
+[Landroid/content/res/ColorStateList;
+Landroid/content/res/CompatibilityInfo$Translator;
+Landroid/content/res/FontResourcesParser$FontFamilyFilesResourceEntry;
+[Landroid/content/res/FontResourcesParser$FontFileResourceEntry;
+Landroid/content/res/GradientColor$GradientColorFactory;
+Landroid/content/res/StringBlock$Height;
+[Landroid/content/res/XmlBlock;
+Landroid/content/rollback/IRollbackManager$Stub$Proxy;
+[Landroid/content/SyncAdapterType;
+[Landroid/content/UndoOwner;
+[Landroid/database/CursorWindow;
+Landroid/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException;
+[Landroid/database/sqlite/SQLiteConnection$Operation;
+Landroid/database/sqlite/SQLiteConnectionPool$1;
+Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder;
+Landroid/database/sqlite/SqliteWrapper;
+Landroid/database/StaleDataException;
+Landroid/graphics/-$$Lambda$ColorSpace$Rgb$iMkODTKa3_8kPZUnZZerD2Lv-yo;
+Landroid/graphics/-$$Lambda$ColorSpace$Rgb$V_0lmM2WEpxGBDV_1G1wvvidn7Y;
+Landroid/graphics/BlendMode$1;
+[Landroid/graphics/ColorSpace;
+Landroid/graphics/ColorSpace$RenderIntent;
+Landroid/graphics/drawable/-$$Lambda$AnimatedVectorDrawable$VectorDrawableAnimatorRT$PzjgSeyQweoFjbEZJP80UteZqm8;
+Landroid/graphics/drawable/-$$Lambda$Drawable$bbJz2VgQAwkXlE27mR8nPMYacEw;
+[Landroid/graphics/drawable/AdaptiveIconDrawable$ChildDrawable;
+Landroid/graphics/drawable/AnimatedImageDrawable$State;
+Landroid/graphics/drawable/AnimatedRotateDrawable$1;
+Landroid/graphics/drawable/AnimatedStateListDrawable$AnimatableTransition;
+Landroid/graphics/drawable/AnimatedStateListDrawable$AnimationDrawableTransition;
+Landroid/graphics/drawable/AnimatedVectorDrawable$2;
+Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorUI;
+Landroid/graphics/drawable/ColorStateListDrawable;
+Landroid/graphics/drawable/ColorStateListDrawable$ColorStateListDrawableState;
+[Landroid/graphics/drawable/Drawable;
+Landroid/graphics/drawable/DrawableContainer$1;
+[Landroid/graphics/drawable/LayerDrawable$ChildDrawable;
+Landroid/graphics/drawable/LevelListDrawable;
+Landroid/graphics/drawable/LevelListDrawable$LevelListState;
+[Landroid/graphics/drawable/RippleForeground;
+[Landroid/graphics/fonts/FontFamily;
+Landroid/graphics/fonts/FontFileUtil;
+[Landroid/graphics/fonts/FontVariationAxis;
+Landroid/graphics/LeakyTypefaceStorage;
+Landroid/graphics/Rect$UnflattenHelper;
+Landroid/gsi/IGsiService;
+Landroid/gsi/IGsiService$Stub;
+Landroid/gsi/IGsiService$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback$Stub;
+Landroid/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricService$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricServiceReceiver$Stub;
+Landroid/hardware/biometrics/IBiometricServiceReceiver$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricServiceReceiverInternal$Stub$Proxy;
+Landroid/hardware/camera2/CameraManager$AvailabilityCallback;
+Landroid/hardware/camera2/CameraManager$CameraManagerGlobal;
+Landroid/hardware/camera2/CameraManager$TorchCallback;
+Landroid/hardware/camera2/impl/CameraDeviceImpl;
+Landroid/hardware/camera2/impl/CameraDeviceImpl$CameraHandlerExecutor;
+Landroid/hardware/camera2/legacy/LegacyMetadataMapper;
+Landroid/hardware/camera2/marshal/impl/MarshalQueryableBoolean$MarshalerBoolean;
+[Landroid/hardware/camera2/marshal/MarshalQueryable;
+Landroid/hardware/camera2/utils/ArrayUtils;
+[Landroid/hardware/CameraStatus;
+Landroid/hardware/CameraStatus;
+Landroid/hardware/contexthub/V1_0/ContextHubMsg;
+Landroid/hardware/contexthub/V1_0/MemRange;
+Landroid/hardware/contexthub/V1_0/PhysicalSensor;
+Landroid/hardware/display/-$$Lambda$NightDisplayListener$sOK1HmSbMnFLzc4SdDD1WpVWJiI;
+Landroid/hardware/display/IVirtualDisplayCallback$Stub;
+Landroid/hardware/display/IVirtualDisplayCallback$Stub$Proxy;
+Landroid/hardware/display/NightDisplayListener;
+Landroid/hardware/display/NightDisplayListener$1;
+Landroid/hardware/display/Time;
+Landroid/hardware/fingerprint/IFingerprintClientActiveCallback$Stub;
+Landroid/hardware/fingerprint/IFingerprintClientActiveCallback$Stub$Proxy;
+Landroid/hardware/fingerprint/IFingerprintServiceReceiver$Stub$Proxy;
+Landroid/hardware/input/IInputDevicesChangedListener$Stub$Proxy;
+Landroid/hardware/input/ITabletModeChangedListener$Stub;
+Landroid/hardware/input/ITabletModeChangedListener$Stub$Proxy;
+Landroid/hardware/ISensorPrivacyManager$Stub$Proxy;
+Landroid/hardware/location/ContextHubClient;
+Landroid/hardware/location/ContextHubManager$2;
+Landroid/hardware/location/ContextHubManager$3;
+Landroid/hardware/location/ContextHubManager$4;
+Landroid/hardware/location/ContextHubMessage;
+Landroid/hardware/location/ContextHubTransaction;
+Landroid/hardware/location/GeofenceHardwareImpl;
+Landroid/hardware/location/GeofenceHardwareImpl$1;
+Landroid/hardware/location/GeofenceHardwareImpl$2;
+Landroid/hardware/location/GeofenceHardwareImpl$3;
+Landroid/hardware/location/GeofenceHardwareImpl$GeofenceTransition;
+Landroid/hardware/location/GeofenceHardwareImpl$Reaper;
+Landroid/hardware/location/GeofenceHardwareMonitorEvent;
+Landroid/hardware/location/GeofenceHardwareRequest;
+[Landroid/hardware/location/GeofenceHardwareRequestParcelable;
+Landroid/hardware/location/GeofenceHardwareRequestParcelable;
+Landroid/hardware/location/GeofenceHardwareService$1;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
+Landroid/hardware/location/IContextHubClient$Stub$Proxy;
+Landroid/hardware/location/IContextHubClientCallback$Stub$Proxy;
+Landroid/hardware/location/IContextHubService$Stub$Proxy;
+Landroid/hardware/location/IContextHubTransactionCallback$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardware;
+Landroid/hardware/location/IGeofenceHardware$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardwareCallback;
+Landroid/hardware/location/IGeofenceHardwareCallback$Stub;
+Landroid/hardware/location/IGeofenceHardwareCallback$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback$Stub;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback$Stub$Proxy;
+[Landroid/hardware/location/MemoryRegion;
+Landroid/hardware/location/NanoApp;
+Landroid/hardware/location/NanoAppBinary;
+Landroid/hardware/location/NanoAppFilter;
+Landroid/hardware/radio/config/V1_1/ModemsConfig;
+Landroid/hardware/radio/config/V1_2/SimSlotStatus;
+Landroid/hardware/radio/deprecated/V1_0/IOemHook$Proxy;
+Landroid/hardware/radio/V1_0/Call;
+Landroid/hardware/radio/V1_0/CallForwardInfo;
+Landroid/hardware/radio/V1_0/CarrierRestrictions;
+Landroid/hardware/radio/V1_0/CdmaCallWaiting;
+Landroid/hardware/radio/V1_0/CdmaInformationRecords;
+Landroid/hardware/radio/V1_0/CdmaSignalInfoRecord;
+Landroid/hardware/radio/V1_0/CdmaSmsAddress;
+Landroid/hardware/radio/V1_0/CdmaSmsMessage;
+Landroid/hardware/radio/V1_0/CdmaSmsSubaddress;
+Landroid/hardware/radio/V1_0/CellIdentity;
+Landroid/hardware/radio/V1_0/CellIdentityCdma;
+Landroid/hardware/radio/V1_0/CellIdentityGsm;
+Landroid/hardware/radio/V1_0/CellIdentityTdscdma;
+Landroid/hardware/radio/V1_0/CellIdentityWcdma;
+Landroid/hardware/radio/V1_0/CellInfo;
+Landroid/hardware/radio/V1_0/CellInfoGsm;
+Landroid/hardware/radio/V1_0/CellInfoLte;
+Landroid/hardware/radio/V1_0/CellInfoTdscdma;
+Landroid/hardware/radio/V1_0/CellInfoWcdma;
+Landroid/hardware/radio/V1_0/DataCallFailCause;
+Landroid/hardware/radio/V1_0/GsmSmsMessage;
+Landroid/hardware/radio/V1_0/HardwareConfigModem;
+Landroid/hardware/radio/V1_0/HardwareConfigSim;
+Landroid/hardware/radio/V1_0/ImsSmsMessage;
+Landroid/hardware/radio/V1_0/LastCallFailCauseInfo;
+Landroid/hardware/radio/V1_0/LceDataInfo;
+Landroid/hardware/radio/V1_0/LceStatusInfo;
+Landroid/hardware/radio/V1_0/NeighboringCell;
+Landroid/hardware/radio/V1_0/OperatorInfo;
+Landroid/hardware/radio/V1_0/PcoDataInfo;
+Landroid/hardware/radio/V1_0/SendSmsResult;
+Landroid/hardware/radio/V1_0/SignalStrength;
+Landroid/hardware/radio/V1_0/SimApdu;
+Landroid/hardware/radio/V1_0/SimRefreshResult;
+Landroid/hardware/radio/V1_0/StkCcUnsolSsResult;
+Landroid/hardware/radio/V1_0/SuppSvcNotification;
+Landroid/hardware/radio/V1_0/UusInfo;
+Landroid/hardware/radio/V1_1/IRadio$Proxy;
+Landroid/hardware/radio/V1_1/KeepaliveStatus;
+Landroid/hardware/radio/V1_1/NetworkScanResult;
+Landroid/hardware/radio/V1_2/CellIdentityCdma;
+Landroid/hardware/radio/V1_2/CellIdentityGsm;
+Landroid/hardware/radio/V1_2/CellIdentityLte;
+Landroid/hardware/radio/V1_2/CellIdentityTdscdma;
+Landroid/hardware/radio/V1_2/CellIdentityWcdma;
+Landroid/hardware/radio/V1_2/CellInfoCdma;
+Landroid/hardware/radio/V1_2/CellInfoGsm;
+Landroid/hardware/radio/V1_2/CellInfoLte;
+Landroid/hardware/radio/V1_2/CellInfoTdscdma;
+Landroid/hardware/radio/V1_2/CellInfoWcdma;
+Landroid/hardware/radio/V1_2/LinkCapacityEstimate;
+Landroid/hardware/radio/V1_2/NetworkScanResult;
+Landroid/hardware/radio/V1_2/PhysicalChannelConfig;
+Landroid/hardware/radio/V1_2/TdscdmaSignalStrength;
+Landroid/hardware/radio/V1_4/CardStatus;
+Landroid/hardware/radio/V1_4/CarrierRestrictionsWithPriority;
+Landroid/hardware/radio/V1_4/CellInfo;
+Landroid/hardware/radio/V1_4/DataRegStateResult;
+Landroid/hardware/radio/V1_4/DataRegStateResult$VopsInfo;
+Landroid/hardware/radio/V1_4/EmergencyNumber;
+Landroid/hardware/radio/V1_4/NetworkScanResult;
+Landroid/hardware/radio/V1_4/NrIndicators;
+Landroid/hardware/radio/V1_4/PhysicalChannelConfig;
+Landroid/hardware/radio/V1_4/SetupDataCallResult;
+Landroid/hardware/radio/V1_4/SignalStrength;
+Landroid/hardware/SensorAdditionalInfo;
+Landroid/hardware/SensorPrivacyManager$1;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback$Stub;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback$Stub$Proxy;
+[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;
+[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;
+Landroid/hardware/soundtrigger/SoundTriggerModule$NativeEventHandlerDelegate;
+Landroid/hardware/soundtrigger/SoundTriggerModule$NativeEventHandlerDelegate$1;
+Landroid/hardware/SystemSensorManager$TriggerEventQueue;
+Landroid/hardware/TriggerEvent;
+Landroid/hardware/usb/UsbAccessory;
+Landroid/icu/impl/locale/InternalLocaleBuilder;
+Landroid/icu/impl/locale/LanguageTag;
+Landroid/icu/text/BidiLine;
+Landroid/icu/text/Edits$Iterator;
+Landroid/inputmethodservice/-$$Lambda$InputMethodService$8T9TmAUIN7vW9eU6kTg8309_d4E;
+Landroid/inputmethodservice/-$$Lambda$InputMethodService$wp8DeVGx_WDOPw4F6an7QbwVxf0;
+Landroid/inputmethodservice/AbstractInputMethodService;
+Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodImpl;
+Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodSessionImpl;
+Landroid/inputmethodservice/IInputMethodSessionWrapper;
+Landroid/inputmethodservice/IInputMethodSessionWrapper$ImeInputEventReceiver;
+Landroid/inputmethodservice/IInputMethodWrapper;
+Landroid/inputmethodservice/IInputMethodWrapper$InputMethodSessionCallbackWrapper;
+Landroid/inputmethodservice/InputMethodService;
+Landroid/inputmethodservice/InputMethodService$InputMethodSessionImpl;
+Landroid/inputmethodservice/InputMethodService$Insets;
+Landroid/inputmethodservice/InputMethodService$SettingsObserver;
+Landroid/internal/hidl/base/V1_0/DebugInfo;
+Landroid/location/Country;
+Landroid/location/CountryDetector$ListenerTransport;
+Landroid/location/Criteria;
+Landroid/location/Geofence;
+Landroid/location/GnssClock;
+Landroid/location/GnssMeasurement;
+Landroid/location/GnssMeasurementCorrections;
+Landroid/location/IBatchedLocationCallback$Stub$Proxy;
+Landroid/location/ICountryListener$Stub;
+Landroid/location/ICountryListener$Stub$Proxy;
+Landroid/location/IFusedGeofenceHardware$Stub;
+Landroid/location/IFusedGeofenceHardware$Stub$Proxy;
+Landroid/location/IGeocodeProvider;
+Landroid/location/IGeocodeProvider$Stub;
+Landroid/location/IGeocodeProvider$Stub$Proxy;
+Landroid/location/IGeofenceProvider;
+Landroid/location/IGeofenceProvider$Stub;
+Landroid/location/IGeofenceProvider$Stub$Proxy;
+Landroid/location/IGnssMeasurementsListener$Stub$Proxy;
+Landroid/location/IGnssNavigationMessageListener$Stub$Proxy;
+Landroid/location/IGnssStatusListener$Stub$Proxy;
+Landroid/location/IGpsGeofenceHardware$Stub$Proxy;
+Landroid/location/ILocationListener$Stub$Proxy;
+Landroid/location/LocationManager$GnssStatusListenerTransport$1;
+Landroid/media/-$$Lambda$MediaCodecInfo$VideoCapabilities$DpgwEn-gVFZT9EtP3qcxpiA2G0M;
+[Landroid/media/AudioDeviceInfo;
+Landroid/media/AudioFocusInfo;
+Landroid/media/AudioFocusRequest;
+Landroid/media/AudioManager$BlockingFocusResultReceiver;
+Landroid/media/AudioManager$SafeWaitObject;
+[Landroid/media/AudioPatch;
+Landroid/media/audiopolicy/AudioPolicyConfig;
+Landroid/media/audiopolicy/IAudioPolicyCallback;
+Landroid/media/audiopolicy/IAudioPolicyCallback$Stub;
+Landroid/media/audiopolicy/IAudioPolicyCallback$Stub$Proxy;
+[Landroid/media/AudioPort;
+Landroid/media/AudioRecordingMonitorImpl;
+Landroid/media/AudioRecordingMonitorImpl$1;
+[Landroid/media/ExifInterface$Rational;
+Landroid/media/ExifInterface$Rational;
+Landroid/media/IAudioFocusDispatcher$Stub$Proxy;
+Landroid/media/IAudioServerStateDispatcher$Stub$Proxy;
+Landroid/media/IMediaRouter2Client$Stub;
+Landroid/media/IMediaRouter2Client$Stub$Proxy;
+Landroid/media/IMediaRouter2Manager$Stub;
+Landroid/media/IMediaRouter2Manager$Stub$Proxy;
+Landroid/media/IPlaybackConfigDispatcher$Stub$Proxy;
+Landroid/media/IRecordingConfigDispatcher$Stub$Proxy;
+Landroid/media/IRemoteVolumeController$Stub$Proxy;
+Landroid/media/IVolumeController$Stub$Proxy;
+Landroid/media/MediaCodec$BufferMap$CodecBuffer;
+[Landroid/media/MediaCodecInfo;
+[Landroid/media/MediaCodecInfo$CodecCapabilities;
+[Landroid/media/MediaCodecInfo$CodecProfileLevel;
+Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;
+Landroid/media/MediaHTTPService;
+Landroid/media/MediaPlayer$6;
+Landroid/media/MediaPlayer$DrmInfo;
+Landroid/media/MediaPlayer$EventHandler$1;
+Landroid/media/MediaPlayer$EventHandler$2;
+[Landroid/media/MediaPlayer$TrackInfo;
+Landroid/media/MediaPlayer$TrackInfo;
+Landroid/media/MediaRoute2Info;
+Landroid/media/MediaRouter$RouteGroup;
+Landroid/media/MediaRouterClientState;
+Landroid/media/MediaRouterClientState$RouteInfo;
+[Landroid/media/MediaTimeProvider$OnMediaTimeListener;
+Landroid/media/NativeRoutingEventHandlerDelegate;
+Landroid/media/projection/IMediaProjection$Stub;
+Landroid/media/projection/IMediaProjection$Stub$Proxy;
+Landroid/media/projection/IMediaProjectionManager$Stub$Proxy;
+Landroid/media/projection/IMediaProjectionWatcherCallback$Stub$Proxy;
+Landroid/media/projection/MediaProjectionManager$CallbackDelegate;
+Landroid/media/Rating;
+Landroid/media/Ringtone;
+Landroid/media/Session2Token;
+Landroid/media/session/IActiveSessionsListener$Stub$Proxy;
+Landroid/media/session/ICallback$Stub$Proxy;
+Landroid/media/session/IOnMediaKeyListener$Stub$Proxy;
+Landroid/media/session/IOnVolumeKeyLongPressListener$Stub$Proxy;
+Landroid/media/session/ISessionCallback$Stub$Proxy;
+Landroid/media/session/ISessionControllerCallback$Stub$Proxy;
+Landroid/media/session/MediaController$PlaybackInfo;
+Landroid/media/session/MediaSessionManager$CallbackStub;
+Landroid/media/SubtitleController;
+Landroid/media/SubtitleController$2;
+Landroid/media/SubtitleData;
+Landroid/media/SubtitleTrack;
+Landroid/media/TimedMetaData;
+Landroid/media/TimedText;
+Landroid/net/-$$Lambda$Network$KD6DxaMRJIcajhj36TU1K7lJnHQ;
+Landroid/net/-$$Lambda$NetworkFactory$HfslgqyaKc_n0wXX5_qRYVZoGfI;
+Landroid/net/-$$Lambda$NetworkStats$xvFSsVoR0k5s7Fhw1yPDPVIpx8A;
+Landroid/net/ConnectionInfo;
+Landroid/net/DataUsageRequest;
+Landroid/net/EventLogTags;
+Landroid/net/IIpConnectivityMetrics$Stub$Proxy;
+Landroid/net/INetdEventCallback$Stub$Proxy;
+Landroid/net/INetworkManagementEventObserver$Stub$Proxy;
+Landroid/net/INetworkPolicyListener$Stub$Proxy;
+Landroid/net/INetworkRecommendationProvider;
+Landroid/net/INetworkRecommendationProvider$Stub;
+Landroid/net/INetworkRecommendationProvider$Stub$Proxy;
+Landroid/net/INetworkScoreCache$Stub$Proxy;
+Landroid/net/ISocketKeepaliveCallback$Stub;
+Landroid/net/ISocketKeepaliveCallback$Stub$Proxy;
+Landroid/net/ITetheringEventCallback$Stub;
+Landroid/net/ITetheringEventCallback$Stub$Proxy;
+Landroid/net/ITetheringStatsProvider$Stub$Proxy;
+Landroid/net/LinkProperties$CompareResult;
+Landroid/net/metrics/ApfStats;
+Landroid/net/metrics/ConnectStats;
+Landroid/net/metrics/DhcpClientEvent;
+Landroid/net/metrics/DnsEvent;
+Landroid/net/metrics/IpManagerEvent;
+Landroid/net/metrics/IpReachabilityEvent;
+Landroid/net/metrics/NetworkEvent;
+Landroid/net/metrics/NetworkMetrics$Metrics;
+Landroid/net/metrics/NetworkMetrics$Summary;
+Landroid/net/metrics/RaEvent;
+Landroid/net/metrics/ValidationProbeEvent;
+Landroid/net/metrics/WakeupStats;
+[Landroid/net/Network;
+[Landroid/net/NetworkCapabilities;
+[Landroid/net/NetworkKey;
+[Landroid/net/NetworkPolicy;
+Landroid/net/NetworkPolicyManager$1;
+[Landroid/net/NetworkState;
+Landroid/net/NetworkStatsHistory$Entry;
+Landroid/net/NetworkStatsHistory$ParcelUtils;
+Landroid/net/nsd/INsdManager$Stub$Proxy;
+Landroid/net/nsd/NsdManager$ServiceHandler;
+Landroid/net/PacProxySelector;
+Landroid/net/RssiCurve;
+[Landroid/net/ScoredNetwork;
+Landroid/net/shared/Inet4AddressUtils;
+Landroid/net/shared/InetAddressUtils;
+Landroid/net/sip/ISipService$Stub$Proxy;
+Landroid/net/SntpClient;
+Landroid/net/SntpClient$InvalidServerReplyException;
+Landroid/net/StaticIpConfiguration$Builder;
+[Landroid/net/UidRange;
+[Landroid/net/Uri;
+[Landroid/net/wifi/AnqpInformationElement;
+Landroid/net/wifi/AnqpInformationElement;
+Landroid/net/wifi/hotspot2/IProvisioningCallback$Stub;
+Landroid/net/wifi/hotspot2/IProvisioningCallback$Stub$Proxy;
+Landroid/net/wifi/IDppCallback$Stub;
+Landroid/net/wifi/IDppCallback$Stub$Proxy;
+Landroid/net/wifi/INetworkRequestMatchCallback$Stub;
+Landroid/net/wifi/INetworkRequestMatchCallback$Stub$Proxy;
+Landroid/net/wifi/IOnWifiUsabilityStatsListener$Stub;
+Landroid/net/wifi/IOnWifiUsabilityStatsListener$Stub$Proxy;
+Landroid/net/wifi/ISoftApCallback$Stub$Proxy;
+Landroid/net/wifi/ITrafficStateCallback$Stub$Proxy;
+Landroid/net/wifi/IWifiScanner$Stub$Proxy;
+Landroid/net/wifi/p2p/IWifiP2pManager$Stub$Proxy;
+Landroid/net/wifi/rtt/IRttCallback$Stub;
+Landroid/net/wifi/rtt/IRttCallback$Stub$Proxy;
+Landroid/net/wifi/rtt/RangingRequest;
+[Landroid/net/wifi/ScanResult$InformationElement;
+[Landroid/net/wifi/ScanResult$RadioChainInfo;
+Landroid/net/wifi/SupplicantState$2;
+Landroid/net/wifi/WifiConfiguration$KeyMgmt;
+Landroid/net/wifi/WifiManager$MulticastLock;
+Landroid/net/wifi/WifiManager$SoftApCallbackProxy;
+Landroid/net/wifi/WifiManager$TrafficStateCallbackProxy;
+Landroid/net/wifi/WifiNetworkScoreCache$CacheListener$1;
+Landroid/net/wifi/WifiNetworkSpecifier;
+Landroid/net/wifi/WifiNetworkSuggestion;
+[Landroid/net/wifi/WifiScanner$ChannelSpec;
+Landroid/net/wifi/WifiScanner$OperationResult;
+Landroid/net/wifi/WifiScanner$ParcelableScanData;
+Landroid/net/wifi/WifiScanner$ParcelableScanResults;
+[Landroid/net/wifi/WifiScanner$ScanSettings$HiddenNetwork;
+Landroid/net/wifi/WifiScanner$ScanSettings$HiddenNetwork;
+Landroid/nfc/BeamShareData;
+Landroid/nfc/IAppCallback$Stub$Proxy;
+Landroid/nfc/INfcUnlockHandler$Stub;
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;
+Landroid/nfc/ITagRemovedCallback$Stub;
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;
+Landroid/nfc/Tag;
+Landroid/nfc/TechListParcel;
+Landroid/os/-$$Lambda$HidlSupport$GHxmwrIWiKN83tl6aMQt_nV5hiw;
+Landroid/os/-$$Lambda$PowerManager$WakeLock$VvFzmRZ4ZGlXx7u3lSAJ_T-YUjw;
+Landroid/os/-$$Lambda$StrictMode$AndroidBlockGuardPolicy$FxZGA9KtfTewqdcxlUwvIe5Nx9I;
+Landroid/os/-$$Lambda$StrictMode$UFC_nI1x6u8ZwMQmA7bmj9NHZz4;
+Landroid/os/AsyncTask$5;
+Landroid/os/BatteryProperty;
+Landroid/os/BatterySaverPolicyConfig;
+Landroid/os/BatteryStats$2;
+Landroid/os/BatteryStats$HistoryPrinter;
+[Landroid/os/Bundle;
+Landroid/os/connectivity/CellularBatteryStats;
+Landroid/os/connectivity/GpsBatteryStats;
+Landroid/os/connectivity/WifiBatteryStats;
+[Landroid/os/Debug$MemoryInfo;
+Landroid/os/FileUtils$1;
+Landroid/os/GraphicsEnvironment$1;
+Landroid/os/health/HealthKeys$Constant;
+[Landroid/os/health/HealthKeys$SortedIntArray;
+Landroid/os/health/HealthKeys$SortedIntArray;
+Landroid/os/health/HealthStatsWriter;
+Landroid/os/IDeviceIdentifiersPolicyService$Stub$Proxy;
+Landroid/os/IMaintenanceActivityListener$Stub;
+Landroid/os/IMaintenanceActivityListener$Stub$Proxy;
+Landroid/os/INetworkActivityListener;
+Landroid/os/INetworkActivityListener$Stub;
+Landroid/os/INetworkActivityListener$Stub$Proxy;
+Landroid/os/IProgressListener$Stub$Proxy;
+Landroid/os/IRemoteCallback$Stub$Proxy;
+Landroid/os/IServiceManager$Stub;
+Landroid/os/IServiceManager$Stub$Proxy;
+Landroid/os/IThermalEventListener$Stub$Proxy;
+Landroid/os/IThermalService$Stub$Proxy;
+Landroid/os/IThermalStatusListener$Stub$Proxy;
+Landroid/os/IVoldTaskListener$Stub;
+Landroid/os/IVoldTaskListener$Stub$Proxy;
+Landroid/os/MessageQueue$FileDescriptorRecord;
+[Landroid/os/MessageQueue$IdleHandler;
+Landroid/os/NetworkOnMainThreadException;
+[Landroid/os/Parcelable;
+[Landroid/os/ParcelFileDescriptor;
+[Landroid/os/ParcelUuid;
+[Landroid/os/PatternMatcher;
+[Landroid/os/PersistableBundle;
+[Landroid/os/RegistrantList;
+Landroid/os/RemoteCallback$1;
+Landroid/os/RemoteCallback$2;
+Landroid/os/SharedMemory$Closer;
+Landroid/os/SharedMemory$MemoryRegistration;
+Landroid/os/SharedMemory$Unmapper;
+Landroid/os/StatsDimensionsValue;
+[Landroid/os/storage/DiskInfo;
+Landroid/os/storage/IObbActionListener$Stub$Proxy;
+Landroid/os/storage/IStorageEventListener$Stub$Proxy;
+Landroid/os/storage/IStorageShutdownObserver$Stub;
+Landroid/os/storage/IStorageShutdownObserver$Stub$Proxy;
+[Landroid/os/storage/StorageVolume;
+[Landroid/os/storage/VolumeInfo;
+[Landroid/os/storage/VolumeRecord;
+Landroid/os/strictmode/ContentUriWithoutPermissionViolation;
+Landroid/os/strictmode/CredentialProtectedWhileLockedViolation;
+Landroid/os/strictmode/CustomViolation;
+Landroid/os/strictmode/DiskWriteViolation;
+Landroid/os/strictmode/ExplicitGcViolation;
+Landroid/os/strictmode/ImplicitDirectBootViolation;
+Landroid/os/strictmode/IntentReceiverLeakedViolation;
+Landroid/os/strictmode/LeakedClosableViolation;
+Landroid/os/strictmode/NetworkViolation;
+Landroid/os/strictmode/ResourceMismatchViolation;
+Landroid/os/strictmode/ServiceConnectionLeakedViolation;
+Landroid/os/strictmode/SqliteObjectLeakedViolation;
+Landroid/os/strictmode/UnbufferedIoViolation;
+Landroid/os/strictmode/UntaggedSocketViolation;
+Landroid/os/SystemService;
+Landroid/os/SystemService$State;
+[Landroid/os/WorkSource;
+Landroid/permission/-$$Lambda$PermissionControllerManager$Iy-7wiKMCV-MFSPGyIJxP_DSf8E;
+Landroid/permission/-$$Lambda$PermissionControllerManager$yYvntg8BoN_6kTWlcdYA7GFZJjs;
+Landroid/permission/IPermissionController$Stub;
+Landroid/permission/IPermissionManager$Stub;
+Landroid/permission/IPermissionManager$Stub$Proxy;
+Landroid/preference/PreferenceInflater;
+Landroid/preference/PreferenceScreen;
+Landroid/print/IPrintSpooler;
+Landroid/print/IPrintSpooler$Stub;
+Landroid/print/IPrintSpooler$Stub$Proxy;
+Landroid/print/PrintJobInfo;
+Landroid/printservice/PrintServiceInfo;
+Landroid/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder;
+Landroid/privacy/internal/rappor/RapporConfig;
+Landroid/privacy/internal/rappor/RapporEncoder;
+Landroid/provider/BlockedNumberContract;
+Landroid/provider/BlockedNumberContract$SystemContract;
+Landroid/provider/ContactsContract$PhoneLookup;
+Landroid/provider/DeviceConfig$2;
+Landroid/provider/Downloads;
+[Landroid/provider/FontsContract$FontInfo;
+Landroid/provider/Telephony$Mms;
+Landroid/security/IKeyChainService$Stub;
+Landroid/security/IKeyChainService$Stub$Proxy;
+Landroid/security/keymaster/KeymasterBooleanArgument;
+Landroid/security/keymaster/KeymasterDateArgument;
+Landroid/security/keymaster/KeymasterDefs;
+Landroid/security/keymaster/KeymasterLongArgument;
+Landroid/security/KeyStore$KeyCharacteristicsCallbackResult;
+Landroid/security/KeyStore$KeyCharacteristicsPromise;
+Landroid/security/KeyStore$KeystoreResultPromise;
+Landroid/security/KeyStore$OperationPromise;
+Landroid/security/KeyStore$State;
+Landroid/security/keystore/AndroidKeyStoreLoadStoreParameter;
+Landroid/security/keystore/AndroidKeyStorePrivateKey;
+Landroid/security/keystore/AndroidKeyStoreSecretKey;
+Landroid/security/keystore/ArrayUtils;
+Landroid/security/keystore/KeyProperties$Digest;
+Landroid/security/keystore/KeyStoreConnectException;
+Landroid/security/keystore/KeystoreResponse;
+Landroid/security/keystore/recovery/KeyChainProtectionParams;
+Landroid/security/keystore/recovery/KeyChainSnapshot;
+Landroid/security/keystore/recovery/KeyDerivationParams;
+Landroid/security/keystore/recovery/RecoveryCertPath;
+Landroid/security/keystore/recovery/WrappedApplicationKey;
+Landroid/security/keystore/recovery/X509CertificateParsingUtils;
+Landroid/security/keystore/UserNotAuthenticatedException;
+Landroid/security/keystore/Utils;
+Landroid/security/net/config/Domain;
+Landroid/security/net/config/ResourceCertificateSource;
+Landroid/security/net/config/TrustedCertificateStoreAdapter;
+Landroid/security/net/config/UserCertificateSource$NoPreloadHolder;
+Landroid/security/net/config/WfaCertificateSource;
+Landroid/security/net/config/WfaCertificateSource$NoPreloadHolder;
+Landroid/service/appprediction/IPredictionService;
+Landroid/service/appprediction/IPredictionService$Stub;
+Landroid/service/appprediction/IPredictionService$Stub$Proxy;
+Landroid/service/autofill/augmented/IAugmentedAutofillService;
+Landroid/service/autofill/augmented/IAugmentedAutofillService$Stub;
+Landroid/service/autofill/augmented/IAugmentedAutofillService$Stub$Proxy;
+Landroid/service/autofill/AutofillServiceInfo;
+Landroid/service/autofill/FillResponse;
+Landroid/service/autofill/IAutoFillService;
+Landroid/service/autofill/IAutoFillService$Stub;
+Landroid/service/autofill/IAutoFillService$Stub$Proxy;
+Landroid/service/autofill/UserData;
+Landroid/service/contentcapture/ContentCaptureServiceInfo;
+Landroid/service/contentcapture/FlushMetrics;
+Landroid/service/contentcapture/IContentCaptureService;
+Landroid/service/contentcapture/IContentCaptureService$Stub;
+Landroid/service/contentcapture/IContentCaptureService$Stub$Proxy;
+Landroid/service/dreams/IDreamService;
+Landroid/service/dreams/IDreamService$Stub;
+Landroid/service/dreams/IDreamService$Stub$Proxy;
+[Landroid/service/euicc/EuiccProfileInfo;
+Landroid/service/gatekeeper/GateKeeperResponse;
+[Landroid/service/notification/Condition;
+Landroid/service/notification/IConditionProvider$Stub$Proxy;
+Landroid/service/notification/INotificationListener$Stub$Proxy;
+Landroid/service/notification/ScheduleCalendar;
+Landroid/service/notification/SnoozeCriterion;
+[Landroid/service/notification/StatusBarNotification;
+[Landroid/service/notification/ZenModeConfig$ZenRule;
+Landroid/service/persistentdata/IPersistentDataBlockService$Stub$Proxy;
+Landroid/service/textclassifier/ITextClassifierCallback$Stub;
+Landroid/service/textclassifier/ITextClassifierCallback$Stub$Proxy;
+Landroid/service/trust/ITrustAgentService;
+Landroid/service/trust/ITrustAgentService$Stub;
+Landroid/service/trust/ITrustAgentService$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionService$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionSession$Stub;
+Landroid/service/voice/IVoiceInteractionSession$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionSessionService$Stub$Proxy;
+Landroid/service/voice/VoiceInteractionServiceInfo;
+Landroid/service/vr/IPersistentVrStateCallbacks$Stub$Proxy;
+Landroid/service/vr/IVrManager$Stub$Proxy;
+Landroid/service/vr/IVrStateCallbacks$Stub$Proxy;
+Landroid/service/wallpaper/IWallpaperEngine$Stub$Proxy;
+Landroid/service/wallpaper/IWallpaperService$Stub$Proxy;
+Landroid/stats/devicepolicy/nano/StringList;
+Landroid/sysprop/ProductProperties;
+[Landroid/system/StructPollfd;
+Landroid/system/suspend/WakeLockInfo;
+Landroid/telecom/-$$Lambda$cyYWqCYT05eM23eLVm4oQ5DrYjw;
+Landroid/telecom/-$$Lambda$qa4s1Fm2YuohEunaJUJcmJXDXG0;
+Landroid/telecom/AudioState;
+Landroid/telecom/Conference;
+Landroid/telecom/Conferenceable;
+Landroid/telecom/Connection;
+Landroid/telecom/Connection$FailureSignalingConnection;
+Landroid/telecom/ConnectionRequest;
+Landroid/telecom/ConnectionService$1;
+Landroid/telecom/ConnectionService$2;
+Landroid/telecom/ConnectionService$3;
+Landroid/telecom/ConnectionService$4;
+Landroid/telecom/ConnectionService$5;
+Landroid/telecom/ConnectionServiceAdapter;
+Landroid/telecom/DisconnectCause;
+Landroid/telecom/Logging/-$$Lambda$L5F_SL2jOCUETYvgdB36aGwY50E;
+Landroid/telecom/Logging/-$$Lambda$SessionManager$hhtZwTEbvO-fLNlAvB6Do9_2gW4;
+Landroid/telecom/Logging/-$$Lambda$SessionManager$VyH2gT1EjIvzDy_C9JfTT60CISM;
+Landroid/telecom/Logging/EventManager;
+Landroid/telecom/Logging/EventManager$Event;
+Landroid/telecom/Logging/EventManager$EventListener;
+Landroid/telecom/Logging/EventManager$EventRecord;
+Landroid/telecom/Logging/Runnable;
+Landroid/telecom/Logging/Runnable$1;
+Landroid/telecom/Logging/Session;
+Landroid/telecom/Logging/Session$Info;
+Landroid/telecom/Logging/SessionManager;
+Landroid/telecom/Logging/SessionManager$ISessionListener;
+Landroid/telecom/ParcelableConference;
+Landroid/telecom/ParcelableConnection;
+Landroid/telecom/RemoteConnectionManager;
+Landroid/telecom/StatusHints;
+Landroid/telecom/VideoProfile;
+Landroid/telephony/-$$Lambda$DataFailCause$djkZSxdG-s-w2L5rQKiGu6OudyY;
+Landroid/telephony/-$$Lambda$MLKtmRGKP3e0WU7x_KyS5-Vg8q4;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$0s34qsuHFsa43jUHrTkD62ni6Ds;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$2cMrwdqnKBpixpApeIX38rmRLak;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$2VMO21pFQN-JN3kpn6vQN1zPFEU;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$4NHt5Shg_DHV-T1IxfcQLHP5-j0;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$aysbwPqxcLV_5w6LP0TzZu2D-ew;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$bELzxgwsPigyVKYkAXBO2BjcSm8;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$dUc3j82sK9P9Zpaq-91n9bk_Rpc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$Hbn6-eZxY2p3rjOfStodI04A8E8;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$ipH9N0fJiGE9EBJHahQeXcCZXzo;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$JalixlMNdjktPsNntP_JT9pymhs;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$jlNX9JiqGSNg9W49vDcKucKdeCI;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$nnG75RvQ1_1KZGJk1ySeCH1JJRg;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$Q2A8FgYlU8_D6PD78tThGut_rTc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$XyayAGWQZC2dNjwr697SfSGBBOc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$yvQnAlFGg5EWDG2vcA9X-4xnalA;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$YY3srkIkMm8vTSFJZHoiKzUUrGs;
+Landroid/telephony/AccessNetworkConstants$AccessNetworkType;
+Landroid/telephony/cdma/CdmaCellLocation;
+Landroid/telephony/CellConfigLte;
+Landroid/telephony/CellIdentityCdma;
+Landroid/telephony/CellIdentityGsm;
+Landroid/telephony/CellIdentityNr;
+Landroid/telephony/CellIdentityTdscdma;
+Landroid/telephony/CellInfoCdma;
+Landroid/telephony/CellInfoGsm;
+Landroid/telephony/CellInfoNr;
+Landroid/telephony/CellInfoTdscdma;
+Landroid/telephony/CellInfoWcdma;
+Landroid/telephony/DataConnectionRealTimeInfo;
+[Landroid/telephony/data/DataProfile;
+Landroid/telephony/data/DataService$DataCallListChangedIndication;
+Landroid/telephony/data/DataService$DeactivateDataCallRequest;
+Landroid/telephony/DataFailCause$1;
+Landroid/telephony/data/IDataService$Stub$Proxy;
+Landroid/telephony/ICellInfoCallback$Stub;
+Landroid/telephony/ICellInfoCallback$Stub$Proxy;
+Landroid/telephony/IFinancialSmsCallback$Stub;
+Landroid/telephony/IFinancialSmsCallback$Stub$Proxy;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$8xq93ap6i0L56Aegaj-ZEUt9ISc;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$AhnK6VJjwgpDNC1GXRrwfgtYvkM;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$iuI3HyNU5eUABA_QRyzQ8Jw2-8g;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$J4VhgcUtd6SivHcdkzpurbTuyLc;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$jAP4lCkBQEdyrlgt5jaNPTlFXlY;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$oDp7ilyKfflFThUCP4Du9EYoDoQ;
+Landroid/telephony/ims/aidl/IImsCapabilityCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsConfigCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsMmTelFeature$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsMmTelListener$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsRegistrationCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsSmsListener$Stub$Proxy;
+[Landroid/telephony/ims/ImsCallForwardInfo;
+Landroid/telephony/ims/ImsExternalCallState;
+[Landroid/telephony/ims/ImsSsInfo;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$cWwTXSDsk-bWPbsDJYI--DUBMnE;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$sbjuTvW-brOSWMR74UInSZEIQB0;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$wwtkoeOtGwMjG5I0-ZTfjNpGU-s;
+Landroid/telephony/INetworkService$Stub$Proxy;
+Landroid/telephony/JapanesePhoneNumberFormatter;
+Landroid/telephony/NeighboringCellInfo;
+Landroid/telephony/PhysicalChannelConfig$Builder;
+[Landroid/telephony/RadioAccessFamily;
+Landroid/telephony/SmsMessage;
+Landroid/telephony/SubscriptionManager$OnOpportunisticSubscriptionsChangedListener;
+Landroid/telephony/SubscriptionManager$OnOpportunisticSubscriptionsChangedListener$1;
+[Landroid/telephony/SubscriptionPlan;
+Landroid/telephony/SubscriptionPlan;
+[Landroid/telephony/UiccAccessRule;
+Landroid/telephony/VisualVoicemailSmsFilterSettings$Builder;
+Landroid/text/CharSequenceCharacterIterator;
+[Landroid/text/DynamicLayout$ChangeWatcher;
+[Landroid/text/FontConfig$Alias;
+[Landroid/text/FontConfig$Family;
+[Landroid/text/FontConfig$Font;
+Landroid/text/format/Formatter$BytesResult;
+Landroid/text/HtmlToSpannedConverter$Big;
+Landroid/text/HtmlToSpannedConverter$Blockquote;
+Landroid/text/HtmlToSpannedConverter$Bold;
+Landroid/text/HtmlToSpannedConverter$Bullet;
+Landroid/text/HtmlToSpannedConverter$Heading;
+Landroid/text/HtmlToSpannedConverter$Italic;
+Landroid/text/HtmlToSpannedConverter$Monospace;
+Landroid/text/HtmlToSpannedConverter$Small;
+Landroid/text/HtmlToSpannedConverter$Strikethrough;
+Landroid/text/HtmlToSpannedConverter$Sub;
+Landroid/text/HtmlToSpannedConverter$Super;
+Landroid/text/HtmlToSpannedConverter$Underline;
+[Landroid/text/Layout$Directions;
+Landroid/text/Layout$HorizontalMeasurementProvider;
+Landroid/text/Layout$TabStops;
+Landroid/text/method/DateKeyListener;
+Landroid/text/method/DateTimeKeyListener;
+Landroid/text/method/DialerKeyListener;
+Landroid/text/method/DigitsKeyListener;
+Landroid/text/method/TimeKeyListener;
+[Landroid/text/PrecomputedText$ParagraphInfo;
+Landroid/text/PrecomputedText$Params$Builder;
+[Landroid/text/Selection$MemoryTextWatcher;
+[Landroid/text/SpanWatcher;
+Landroid/text/style/AccessibilityClickableSpan;
+Landroid/text/style/AccessibilityURLSpan;
+[Landroid/text/style/AlignmentSpan;
+Landroid/text/style/AlignmentSpan$Standard;
+Landroid/text/style/BulletSpan;
+[Landroid/text/style/CharacterStyle;
+[Landroid/text/style/ClickableSpan;
+[Landroid/text/style/LeadingMarginSpan;
+Landroid/text/style/LeadingMarginSpan$LeadingMarginSpan2;
+Landroid/text/style/LeadingMarginSpan$Standard;
+[Landroid/text/style/LineBackgroundSpan;
+Landroid/text/style/LineBackgroundSpan$Standard;
+[Landroid/text/style/LineHeightSpan;
+Landroid/text/style/LineHeightSpan$Standard;
+Landroid/text/style/LineHeightSpan$WithDensity;
+Landroid/text/style/LocaleSpan;
+[Landroid/text/style/MetricAffectingSpan;
+Landroid/text/style/QuoteSpan;
+[Landroid/text/style/ReplacementSpan;
+Landroid/text/style/ScaleXSpan;
+[Landroid/text/style/SpellCheckSpan;
+Landroid/text/style/StrikethroughSpan;
+Landroid/text/style/SubscriptSpan;
+Landroid/text/style/SuggestionRangeSpan;
+[Landroid/text/style/SuggestionSpan;
+Landroid/text/style/SuperscriptSpan;
+[Landroid/text/style/TabStopSpan;
+Landroid/text/style/TtsSpan;
+Landroid/text/style/TtsSpan$TelephoneBuilder;
+Landroid/text/style/TypefaceSpan;
+[Landroid/text/TextWatcher;
+Landroid/text/util/Linkify;
+Landroid/transition/ArcMotion;
+Landroid/transition/ChangeScroll;
+Landroid/transition/CircularPropagation;
+Landroid/transition/Explode;
+Landroid/transition/PatternPathMotion;
+Landroid/transition/Recolor;
+Landroid/transition/Slide;
+Landroid/util/apk/ApkSigningBlockUtils$1;
+[Landroid/util/apk/DataSource;
+Landroid/util/apk/VerityBuilder$BufferedDigester;
+Landroid/util/apk/VerityBuilder$VerityResult;
+[Landroid/util/ArrayMap;
+Landroid/util/BackupUtils;
+Landroid/util/Half;
+Landroid/util/JsonWriter$1;
+Landroid/util/KeyValueSettingObserver;
+Landroid/util/KeyValueSettingObserver$SettingObserver;
+Landroid/util/LauncherIcons;
+Landroid/util/LogWriter;
+Landroid/util/LongArrayQueue;
+Landroid/util/proto/EncodedBuffer;
+Landroid/util/RecurrenceRule;
+Landroid/util/RecurrenceRule$NonrecurringIterator;
+Landroid/util/RecurrenceRule$RecurringIterator;
+Landroid/util/Spline$LinearSpline;
+Landroid/util/UtilConfig;
+Landroid/view/-$$Lambda$1kvF4JuyM42-wmyDVPAIYdPz1jE;
+Landroid/view/-$$Lambda$cZhmLzK8aetUdx4VlP9w5jR7En0;
+Landroid/view/-$$Lambda$dj1hfDQd0iEp_uBDBPEUMMYJJwk;
+Landroid/view/-$$Lambda$PYGleuqIeCxjTD1pJqqx1opFv1g;
+Landroid/view/-$$Lambda$SurfaceView$w68OV7dB_zKVNsA-r0IrAUtyWas;
+Landroid/view/-$$Lambda$ThreadedRenderer$ydBD-R1iP5u-97XYakm-jKvC1b4;
+Landroid/view/-$$Lambda$ViewRootImpl$dznxCZGM2R1fsBljsJKomLjBRoM;
+Landroid/view/-$$Lambda$ViewRootImpl$IReiNMSbDakZSGbIZuL_ifaFWn8;
+Landroid/view/-$$Lambda$ViewRootImpl$YBiqAhbCbXVPSKdbE3K4rH2gpxI;
+Landroid/view/-$$Lambda$ViewRootImpl$zlBUjCwDtoAWMNaHI62DIq-eKFY;
+Landroid/view/-$$Lambda$WindowManagerGlobal$2bR3FsEm4EdRwuXfttH0wA2xOW4;
+Landroid/view/-$$Lambda$WlJa6OPA72p3gYtA3nVKC7Z1tGY;
+Landroid/view/accessibility/-$$Lambda$AccessibilityManager$1$o7fCplskH9NlBwJvkl6NoZ0L_BA;
+Landroid/view/accessibility/-$$Lambda$AccessibilityManager$yzw5NYY7_MfAQ9gLy3mVllchaXo;
+Landroid/view/accessibility/IAccessibilityInteractionConnection$Stub$Proxy;
+Landroid/view/accessibility/IAccessibilityManagerClient$Stub$Proxy;
+Landroid/view/accessibility/WeakSparseArray;
+Landroid/view/accessibility/WeakSparseArray$WeakReferenceWithId;
+Landroid/view/animation/AnticipateInterpolator;
+Landroid/view/animation/AnticipateOvershootInterpolator;
+Landroid/view/animation/BounceInterpolator;
+Landroid/view/animation/ClipRectAnimation;
+Landroid/view/animation/CycleInterpolator;
+Landroid/view/animation/GridLayoutAnimationController;
+Landroid/view/animation/RotateAnimation;
+[Landroid/view/AppTransitionAnimationSpec;
+Landroid/view/AppTransitionAnimationSpec;
+Landroid/view/autofill/IAutoFillManagerClient$Stub$Proxy;
+[Landroid/view/Choreographer$CallbackQueue;
+Landroid/view/contentcapture/ContentCaptureCondition;
+Landroid/view/contentcapture/ContentCaptureContext;
+Landroid/view/contentcapture/DataRemovalRequest;
+[Landroid/view/Display;
+Landroid/view/DragEvent;
+Landroid/view/GestureExclusionTracker$GestureExclusionViewInfo;
+[Landroid/view/HandlerActionQueue$HandlerAction;
+Landroid/view/IAppTransitionAnimationSpecsFuture$Stub;
+Landroid/view/IAppTransitionAnimationSpecsFuture$Stub$Proxy;
+Landroid/view/IDisplayFoldListener$Stub;
+Landroid/view/IDisplayFoldListener$Stub$Proxy;
+Landroid/view/IDockedStackListener$Stub$Proxy;
+Landroid/view/IGraphicsStatsCallback$Stub$Proxy;
+Landroid/view/inputmethod/-$$Lambda$InputMethodManager$dfnCauFoZCf-HfXs1QavrkwWDf0;
+Landroid/view/inputmethod/-$$Lambda$InputMethodManager$iDWn3IGSUFqIcs8Py42UhfrshxI;
+[Landroid/view/inputmethod/CompletionInfo;
+Landroid/view/inputmethod/CompletionInfo;
+Landroid/view/inputmethod/CorrectionInfo;
+Landroid/view/inputmethod/CursorAnchorInfo;
+Landroid/view/inputmethod/ExtractedTextRequest;
+Landroid/view/inputmethod/InputContentInfo;
+Landroid/view/inputmethod/InputMethod;
+Landroid/view/inputmethod/InputMethodSession;
+[Landroid/view/inputmethod/InputMethodSubtype;
+[Landroid/view/InsetsSourceControl;
+Landroid/view/InsetsSourceControl;
+Landroid/view/IOnKeyguardExitResult$Stub;
+Landroid/view/IOnKeyguardExitResult$Stub$Proxy;
+Landroid/view/IPinnedStackController$Stub$Proxy;
+Landroid/view/IPinnedStackListener$Stub$Proxy;
+Landroid/view/IRecentsAnimationRunner;
+Landroid/view/IRecentsAnimationRunner$Stub;
+Landroid/view/IRecentsAnimationRunner$Stub$Proxy;
+Landroid/view/IRemoteAnimationRunner$Stub;
+Landroid/view/IRemoteAnimationRunner$Stub$Proxy;
+Landroid/view/IRotationWatcher$Stub$Proxy;
+Landroid/view/ISystemGestureExclusionListener$Stub;
+Landroid/view/ISystemGestureExclusionListener$Stub$Proxy;
+Landroid/view/IWindow$Stub$Proxy;
+Landroid/view/IWindowSessionCallback$Stub$Proxy;
+Landroid/view/KeyCharacterMap$UnavailableException;
+Landroid/view/LayoutInflater$BlinkLayout;
+Landroid/view/LayoutInflater$BlinkLayout$1;
+Landroid/view/MenuInflater$InflatedOnMenuItemClickListener;
+[Landroid/view/MotionEvent$PointerCoords;
+[Landroid/view/MotionEvent$PointerProperties;
+Landroid/view/PointerIcon$2;
+Landroid/view/RemotableViewMethod;
+Landroid/view/RemoteAnimationDefinition;
+Landroid/view/RemoteAnimationDefinition$RemoteAnimationAdapterEntry;
+Landroid/view/RenderNodeAnimator$DelayedAnimationHelper;
+Landroid/view/RoundScrollbarRenderer;
+Landroid/view/SoundEffectConstants;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$6oTtcn9bDE-u-8FbiyGdntqoQG0;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$sY0w9od2zcl4YFel0lG4VB3vf7I;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$YTQv8oPvlmJL4tITUFD4z4JWKRk;
+Landroid/view/textclassifier/-$$Lambda$OGSS2qx6njxlnp0dnKb4lA3jnw8;
+Landroid/view/textclassifier/-$$Lambda$TextClassificationManager$oweIEhDWxy3_0kZSXp3oRbSuNW4;
+Landroid/view/textclassifier/-$$Lambda$TextClassificationManager$SIydN2POphTO3AmPTLEMmXPLSKY;
+Landroid/view/textclassifier/-$$Lambda$TextClassifierImpl$ftq-sQqJYwUdrdbbr9jz3p4AWos;
+Landroid/view/textclassifier/-$$Lambda$TextClassifierImpl$iSt_Guet-O6Vtdk0MA4z-Z4lzaM;
+Landroid/view/textclassifier/ActionsModelParamsSupplier;
+Landroid/view/textclassifier/ActionsModelParamsSupplier$ActionsModelParams;
+Landroid/view/textclassifier/ActionsSuggestionsHelper;
+Landroid/view/textclassifier/ActionsSuggestionsHelper$PersonEncoder;
+Landroid/view/textclassifier/ConversationAction;
+Landroid/view/textclassifier/ConversationAction$Builder;
+Landroid/view/textclassifier/ConversationActions$Message;
+Landroid/view/textclassifier/ConversationActions$Request;
+Landroid/view/textclassifier/ExtrasUtils;
+Landroid/view/textclassifier/intent/LabeledIntent;
+Landroid/view/textclassifier/intent/LabeledIntent$Result;
+Landroid/view/textclassifier/Log;
+Landroid/view/textclassifier/ModelFileManager$ModelFile;
+Landroid/view/textclassifier/SelectionEvent;
+Landroid/view/textclassifier/SelectionSessionLogger$SignatureParser;
+Landroid/view/textclassifier/SystemTextClassifier$BlockingCallback;
+Landroid/view/textclassifier/SystemTextClassifier$ResponseReceiver;
+Landroid/view/textclassifier/TextClassification$Request;
+Landroid/view/textclassifier/TextClassificationContext;
+Landroid/view/textclassifier/TextClassificationContext$Builder;
+Landroid/view/textclassifier/TextClassificationSessionId;
+Landroid/view/textclassifier/TextClassifier$EntityConfig;
+Landroid/view/textclassifier/TextClassifier$EntityConfig$Builder;
+Landroid/view/textclassifier/TextClassifier$Utils;
+Landroid/view/textclassifier/TextClassifierEvent;
+Landroid/view/textclassifier/TextClassifierEvent$ConversationActionsEvent;
+Landroid/view/textclassifier/TextClassifierEvent$LanguageDetectionEvent;
+Landroid/view/textclassifier/TextClassifierEvent$TextLinkifyEvent;
+Landroid/view/textclassifier/TextClassifierEvent$TextSelectionEvent;
+Landroid/view/textclassifier/TextLanguage;
+Landroid/view/textclassifier/TextLanguage$Builder;
+Landroid/view/textclassifier/TextLanguage$Request;
+Landroid/view/textclassifier/TextLanguage$Request$Builder;
+Landroid/view/textclassifier/TextLinks$Request;
+Landroid/view/textclassifier/TextSelection$Request;
+[Landroid/view/View;
+[Landroid/view/View$AttachInfo$InvalidateInfo;
+Landroid/view/View$AttachInfo$InvalidateInfo;
+Landroid/view/View$CheckForLongPress;
+Landroid/view/View$DeclaredOnClickListener;
+Landroid/view/ViewGroup$ChildListForAccessibility;
+Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;
+Landroid/view/ViewPropertyAnimator$2;
+Landroid/view/ViewPropertyAnimator$3;
+Landroid/view/ViewRootImpl$2;
+Landroid/view/ViewRootImpl$3;
+Landroid/view/ViewRootImpl$CalledFromWrongThreadException;
+Landroid/view/ViewRootImpl$TakenSurfaceHolder;
+Landroid/view/ViewStructure$HtmlInfo;
+Landroid/view/ViewStub$ViewReplaceRunnable;
+Landroid/view/ViewTreeObserver$OnEnterAnimationCompleteListener;
+Landroid/view/ViewTreeObserver$OnWindowAttachListener;
+Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener;
+Landroid/view/ViewTreeObserver$OnWindowShownListener;
+Landroid/view/WindowInsets$Builder;
+Landroid/view/WindowManager$InvalidDisplayException;
+Landroid/widget/-$$Lambda$RemoteViews$SetOnClickResponse$9rKnU2QqCzJhBC39ZrKYXob0-MA;
+Landroid/widget/AbsListView$1;
+Landroid/widget/AbsListView$2;
+Landroid/widget/AbsListView$4;
+Landroid/widget/AbsListView$CheckForTap;
+Landroid/widget/AbsListView$ListItemAccessibilityDelegate;
+Landroid/widget/AbsListView$SelectionBoundsAdjuster;
+Landroid/widget/ActionMenuPresenter$ActionMenuPopupCallback;
+Landroid/widget/ActionMenuView$ActionMenuPresenterCallback;
+Landroid/widget/Chronometer;
+Landroid/widget/Chronometer$1;
+Landroid/widget/Editor$EasyEditPopupWindow;
+Landroid/widget/Editor$SpanController$1;
+Landroid/widget/Editor$SpanController$2;
+[Landroid/widget/Editor$TextRenderNode;
+[Landroid/widget/Editor$TextViewPositionListener;
+Landroid/widget/GridLayout;
+Landroid/widget/GridLayout$7$1;
+[[Landroid/widget/GridLayout$Arc;
+[Landroid/widget/GridLayout$Arc;
+Landroid/widget/GridLayout$Axis;
+Landroid/widget/GridLayout$Axis$1;
+[Landroid/widget/GridLayout$Bounds;
+[Landroid/widget/GridLayout$Interval;
+Landroid/widget/GridLayout$LayoutParams;
+[Landroid/widget/GridLayout$MutableInt;
+[Landroid/widget/GridLayout$Spec;
+Landroid/widget/GridLayout$Spec;
+Landroid/widget/ImageView$ImageDrawableCallback;
+Landroid/widget/ListView$FocusSelector;
+Landroid/widget/PopupMenu$1;
+Landroid/widget/PopupMenu$2;
+Landroid/widget/PopupWindow$3;
+Landroid/widget/ProgressBar$ProgressTintInfo;
+Landroid/widget/ProgressBar$RefreshData;
+Landroid/widget/ProgressBar$RefreshProgressRunnable;
+Landroid/widget/RemoteViews$ActionException;
+Landroid/widget/RemoteViews$AsyncApplyTask;
+Landroid/widget/RemoteViews$MethodArgs;
+Landroid/widget/RemoteViews$OverrideTextColorsAction;
+Landroid/widget/RemoteViews$RemoteViewsContextWrapper;
+Landroid/widget/RemoteViews$RunnableAction;
+Landroid/widget/RemoteViews$SetEmptyView;
+Landroid/widget/RemoteViews$SetIntTagAction;
+Landroid/widget/RemoteViews$SetPendingIntentTemplate;
+Landroid/widget/RemoteViews$SetRemoteInputsAction;
+Landroid/widget/RemoteViews$SetRemoteViewsAdapterIntent;
+Landroid/widget/RemoteViews$SetRemoteViewsAdapterList;
+Landroid/widget/RemoteViews$SetRippleDrawableColor;
+Landroid/widget/RemoteViews$TextViewDrawableAction;
+Landroid/widget/RemoteViews$TextViewSizeAction;
+Landroid/widget/RemoteViews$ViewContentNavigation;
+Landroid/widget/RemoteViews$ViewGroupActionAdd;
+Landroid/widget/RemoteViews$ViewGroupActionRemove;
+[Landroid/widget/SpellChecker$SpellParser;
+Landroid/widget/Switch;
+Landroid/widget/TextView$1;
+[Landroid/widget/TextView$ChangeWatcher;
+Landroid/widget/TextView$Marquee;
+Landroid/widget/Toast$TN;
+Landroid/widget/Toast$TN$1;
+Landroid/widget/Toolbar$SavedState;
+Lcom/android/framework/protobuf/nano/CodedInputByteBufferNano;
+Lcom/android/framework/protobuf/nano/CodedOutputByteBufferNano;
+Lcom/android/framework/protobuf/nano/CodedOutputByteBufferNano$OutOfSpaceException;
+Lcom/android/framework/protobuf/nano/WireFormatNano;
+Lcom/android/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder;
+Lcom/android/i18n/phonenumbers/NumberParseException$ErrorType;
+Lcom/android/i18n/phonenumbers/Phonemetadata$NumberFormat$Builder;
+Lcom/android/i18n/phonenumbers/ShortNumberInfo;
+Lcom/android/ims/-$$Lambda$ImsManager$_6YCQyhjHBSdrm4ZBEMUQ2AAqOY;
+Lcom/android/ims/ImsCall;
+Lcom/android/ims/ImsManager$2;
+Lcom/android/ims/internal/uce/common/UceLong;
+Lcom/android/ims/internal/uce/options/IOptionsListener$Stub;
+Lcom/android/ims/internal/uce/options/IOptionsListener$Stub$Proxy;
+Lcom/android/ims/internal/uce/presence/IPresenceListener$Stub;
+Lcom/android/ims/internal/uce/presence/IPresenceListener$Stub$Proxy;
+Lcom/android/ims/internal/uce/UceServiceBase$UceServiceBinder;
+Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;
+Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub$Proxy;
+Lcom/android/internal/accessibility/AccessibilityShortcutController$ToggleableFrameworkFeatureInfo;
+Lcom/android/internal/app/AlertController$1;
+Lcom/android/internal/app/AlertController$ButtonHandler;
+Lcom/android/internal/app/AlertController$RecycleListView;
+Lcom/android/internal/app/IAppOpsActiveCallback$Stub$Proxy;
+Lcom/android/internal/app/IAppOpsNotedCallback$Stub$Proxy;
+Lcom/android/internal/app/IVoiceActionCheckCallback$Stub;
+Lcom/android/internal/app/IVoiceActionCheckCallback$Stub$Proxy;
+Lcom/android/internal/app/IVoiceInteractionSessionListener$Stub$Proxy;
+Lcom/android/internal/app/IVoiceInteractionSessionShowCallback$Stub;
+Lcom/android/internal/app/IVoiceInteractor$Stub$Proxy;
+Lcom/android/internal/app/MicroAlertController;
+Lcom/android/internal/app/procstats/DumpUtils;
+Lcom/android/internal/app/procstats/IProcessStats$Stub$Proxy;
+Lcom/android/internal/app/procstats/ProcessState$PssAggr;
+Lcom/android/internal/app/procstats/ProcessStats$TotalMemoryUseCollection;
+Lcom/android/internal/app/ResolverActivity$ActionTitle;
+Lcom/android/internal/app/ToolbarActionBar;
+Lcom/android/internal/appwidget/IAppWidgetHost$Stub;
+Lcom/android/internal/appwidget/IAppWidgetHost$Stub$Proxy;
+Lcom/android/internal/app/WindowDecorActionBar;
+Lcom/android/internal/backup/IBackupTransport$Stub$Proxy;
+Lcom/android/internal/content/PackageHelper$1;
+Lcom/android/internal/graphics/ColorUtils;
+Lcom/android/internal/infra/-$$Lambda$7-CJJfrUZBVuXZyYFEWBNh8Mky8;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$6FcEKfZ-7TXLg6dcCU8EMuMNAy4;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$9IBVTCLLZgndvH7fu1P14PW1_1o;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$ocrHd68Md9x6FfAzVQ6w8MAjFqY;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$PendingRequest$IBoaBGXZQEXJr69u3aJF-LCJ42Y;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$YSUzqqi1Pbrg2dlwMGMtKWbGXck;
+Lcom/android/internal/infra/-$$Lambda$EbzSql2RHkXox5Myj8A-7kLC4_A;
+Lcom/android/internal/infra/AbstractRemoteService$MyAsyncPendingRequest;
+Lcom/android/internal/inputmethod/IInputMethodPrivilegedOperations$Stub$Proxy;
+Lcom/android/internal/inputmethod/InputMethodDebug;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperations;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperations$OpsHolder;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry;
+Lcom/android/internal/inputmethod/SubtypeLocaleUtils;
+Lcom/android/internal/location/ILocationProvider;
+Lcom/android/internal/location/ILocationProvider$Stub;
+Lcom/android/internal/location/ILocationProvider$Stub$Proxy;
+Lcom/android/internal/location/ILocationProviderManager$Stub$Proxy;
+Lcom/android/internal/location/nano/GnssLogsProto$GnssLog;
+Lcom/android/internal/location/nano/GnssLogsProto$PowerMetrics;
+Lcom/android/internal/net/INetworkWatchlistManager$Stub$Proxy;
+Lcom/android/internal/net/VpnConfig;
+[Lcom/android/internal/net/VpnInfo;
+Lcom/android/internal/net/VpnProfile;
+Lcom/android/internal/os/-$$Lambda$BinderCallsStats$sqXweH5BoxhmZvI188ctqYiACRk;
+Lcom/android/internal/os/-$$Lambda$sHtqZgGVjxOf9IJdAdZO6gwD_Do;
+Lcom/android/internal/os/BatteryStatsHelper$1;
+Lcom/android/internal/os/BatteryStatsImpl$3;
+Lcom/android/internal/os/BatteryStatsImpl$4;
+[Lcom/android/internal/os/BatteryStatsImpl$Counter;
+[[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounter;
+[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounter;
+[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounterArray;
+[Lcom/android/internal/os/BatteryStatsImpl$StopwatchTimer;
+Lcom/android/internal/os/BatteryStatsImpl$UidToRemove;
+Lcom/android/internal/os/BinderCallsStats$CallStatKey;
+Lcom/android/internal/os/BinderCallsStats$ExportedCallStat;
+Lcom/android/internal/os/BinderDeathDispatcher$RecipientsInfo;
+Lcom/android/internal/os/BinderInternal$CallSession;
+Lcom/android/internal/os/IShellCallback$Stub;
+Lcom/android/internal/os/IShellCallback$Stub$Proxy;
+[Lcom/android/internal/os/KernelCpuSpeedReader;
+Lcom/android/internal/os/KernelCpuThreadReader$ProcessCpuUsage;
+Lcom/android/internal/os/KernelCpuThreadReader$ThreadCpuUsage;
+Lcom/android/internal/os/LooperStats$ExportedEntry;
+[Lcom/android/internal/os/PowerProfile$CpuClusterKey;
+Lcom/android/internal/os/StatsdHiddenApiUsageLogger;
+Lcom/android/internal/os/TransferPipe;
+Lcom/android/internal/os/WifiPowerEstimator;
+Lcom/android/internal/os/WrapperInit;
+Lcom/android/internal/os/ZygoteSecurityException;
+Lcom/android/internal/os/ZygoteServer$UsapPoolRefillAction;
+Lcom/android/internal/policy/DecorView$2;
+Lcom/android/internal/policy/DecorView$3;
+Lcom/android/internal/policy/IKeyguardDismissCallback$Stub;
+Lcom/android/internal/policy/IKeyguardDismissCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardDrawnCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardExitCallback$Stub;
+Lcom/android/internal/policy/IKeyguardExitCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardService$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardStateCallback$Stub$Proxy;
+Lcom/android/internal/policy/IShortcutService$Stub$Proxy;
+[Lcom/android/internal/policy/PhoneWindow$PanelFeatureState;
+Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState;
+Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
+Lcom/android/internal/statusbar/IStatusBarService$Stub$Proxy;
+[Lcom/android/internal/statusbar/NotificationVisibility;
+Lcom/android/internal/statusbar/NotificationVisibility;
+[Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation;
+Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation;
+Lcom/android/internal/telecom/IConnectionService;
+Lcom/android/internal/telecom/IConnectionService$Stub;
+Lcom/android/internal/telecom/IConnectionService$Stub$Proxy;
+Lcom/android/internal/telecom/IInCallService;
+Lcom/android/internal/telecom/IInCallService$Stub;
+Lcom/android/internal/telecom/IInCallService$Stub$Proxy;
+Lcom/android/internal/telecom/IVideoProvider$Stub$Proxy;
+Lcom/android/internal/telecom/RemoteServiceCallback$Stub;
+Lcom/android/internal/telecom/RemoteServiceCallback$Stub$Proxy;
+Lcom/android/internal/telephony/-$$Lambda$MultiSimSettingController$55347QtGjuukX-px3jYZkJd_z3U;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$1TnOMFYcM13ZTJNoLjxguPwVcxw;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$2xgrYNleR8FFzFT8hEQx3mDtZ8g;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$EYZUPU0CYhRoptGCGJ9y78u-jQM;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$Ja9yTBcEYPqTRBIP-hL0otixVeE;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$PONge0j2mBi_ILbtJD_7euF0uoM;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$rpyQeO7zACcc5v4krwU9_qRMHL8;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$ZOtVAnuhxrXl2L906I6eTOentP0;
+Lcom/android/internal/telephony/-$$Lambda$RadioIndication$GND6XxOOm1d_Ro76zEUFjA9OrEA;
+Lcom/android/internal/telephony/-$$Lambda$RIL$803u4JiCud_JSoDndvAhT13ZZqU;
+Lcom/android/internal/telephony/-$$Lambda$RIL$Ir4pOMTf7R0Jtw4O3F7JgMVtXO4;
+Lcom/android/internal/telephony/-$$Lambda$RIL$ZGWeCQ9boMO1_J1_yQ82l_jK-Nc;
+Lcom/android/internal/telephony/-$$Lambda$RIL$zYsQZAc3z9bM5fCaq_J0dn5kjjo;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionController$0y_j8vef67bMEiPQdeWyjuFpPQ8;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$DY4i_CG7hrAeejGLeh3hMUZySnw;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$ecTEeMEIjOEa2z5W3wjqiicibbY;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$qyDxq2AWyReUxdc6HttVGQeDD3Y;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$tLUuQ7lYu8EjRd038qzQlDm-CtA;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$UFyB0ValfLD0rdGDibCjTnGFkeo;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$Y5woGfEDKrozRViLH7WF93qPEno;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$ZTY4uxKw17CHcHQzbBUF7m-dN-E;
+Lcom/android/internal/telephony/-$$Lambda$TelephonyComponentFactory$InjectedComponents$09rMKC8001jAR0zFrzzlPx26Xjs;
+Lcom/android/internal/telephony/-$$Lambda$TelephonyComponentFactory$InjectedComponents$UYUq9z2WZwxqOLXquU0tTNN9wAs;
+Lcom/android/internal/telephony/-$$Lambda$UV1wDVoVlbcxpr8zevj_aMFtUGw;
+Lcom/android/internal/telephony/-$$Lambda$WWHOcG5P4-jgjzPPgLwm-wN15OM;
+Lcom/android/internal/telephony/BlockChecker;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler$1;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler$CallerInfoWorkerHandler;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CookieWrapper;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$QueryPoolException;
+[Lcom/android/internal/telephony/CallForwardInfo;
+[Lcom/android/internal/telephony/CarrierServiceBindHelper$AppBinding;
+Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServiceConnection;
+Lcom/android/internal/telephony/CarrierServicesSmsFilter;
+Lcom/android/internal/telephony/CarrierServicesSmsFilter$CallbackTimeoutHandler;
+Lcom/android/internal/telephony/CarrierSmsUtils;
+Lcom/android/internal/telephony/cat/AppInterface$CommandType;
+Lcom/android/internal/telephony/cat/BerTlv;
+Lcom/android/internal/telephony/cat/BIPClientParams;
+Lcom/android/internal/telephony/cat/CallSetupParams;
+Lcom/android/internal/telephony/cat/CatCmdMessage;
+Lcom/android/internal/telephony/cat/CatResponseMessage;
+[Lcom/android/internal/telephony/cat/CatService;
+Lcom/android/internal/telephony/cat/CatService$1;
+Lcom/android/internal/telephony/cat/CommandParams;
+Lcom/android/internal/telephony/cat/CommandParamsFactory$1;
+Lcom/android/internal/telephony/cat/ComprehensionTlv;
+Lcom/android/internal/telephony/cat/ComprehensionTlvTag;
+Lcom/android/internal/telephony/cat/DisplayTextParams;
+Lcom/android/internal/telephony/cat/DTTZResponseData;
+Lcom/android/internal/telephony/cat/LanguageParams;
+Lcom/android/internal/telephony/cat/LanguageResponseData;
+Lcom/android/internal/telephony/cat/LaunchBrowserParams;
+Lcom/android/internal/telephony/cat/ResultCode;
+Lcom/android/internal/telephony/cat/RilMessage;
+[Lcom/android/internal/telephony/cat/RilMessageDecoder;
+Lcom/android/internal/telephony/cat/ValueParser;
+Lcom/android/internal/telephony/cdma/CdmaCallWaitingNotification;
+[Lcom/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo;
+Lcom/android/internal/telephony/cdma/SmsMessage;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameData;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameData$Builder;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;
+Lcom/android/internal/telephony/Connection$Listener;
+Lcom/android/internal/telephony/Connection$PostDialState;
+Lcom/android/internal/telephony/dataconnection/-$$Lambda$DataConnection$-tFSpFGzTv_UdpzJlTMOvg8VO98;
+Lcom/android/internal/telephony/dataconnection/-$$Lambda$XZAGhHrbkIDyusER4MAM6luKcT0;
+Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;
+Lcom/android/internal/telephony/dataconnection/DataConnection$UpdateLinkPropertyResult;
+Lcom/android/internal/telephony/dataconnection/DataEnabledOverride;
+Lcom/android/internal/telephony/dataconnection/DataEnabledSettings$1;
+Lcom/android/internal/telephony/dataconnection/DataEnabledSettings$2;
+Lcom/android/internal/telephony/dataconnection/DcNetworkAgent;
+Lcom/android/internal/telephony/dataconnection/DcTracker$4;
+Lcom/android/internal/telephony/dataconnection/DcTracker$ProvisionNotificationBroadcastReceiver;
+Lcom/android/internal/telephony/dataconnection/KeepaliveStatus;
+[Lcom/android/internal/telephony/dataconnection/TelephonyNetworkFactory;
+Lcom/android/internal/telephony/dataconnection/TransportManager$HandoverParams;
+Lcom/android/internal/telephony/DriverCall;
+Lcom/android/internal/telephony/DriverCall$State;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$1;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$10;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$11;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$12;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$13;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$2;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$3;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$5;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$6;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$7;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$8;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$9;
+Lcom/android/internal/telephony/euicc/EuiccConnector$DeleteRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$DownloadRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$GetDefaultListRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$GetMetadataRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$SwitchRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$UpdateNicknameRequest;
+Lcom/android/internal/telephony/euicc/IEuiccController$Stub$Proxy;
+Lcom/android/internal/telephony/GsmCdmaCallTracker$2;
+Lcom/android/internal/telephony/GsmCdmaCallTracker$3;
+[Lcom/android/internal/telephony/GsmCdmaConnection;
+Lcom/android/internal/telephony/GsmCdmaPhone$Cfu;
+Lcom/android/internal/telephony/gsm/GsmMmiCode;
+[Lcom/android/internal/telephony/gsm/SmsBroadcastConfigInfo;
+Lcom/android/internal/telephony/gsm/SmsMessage;
+Lcom/android/internal/telephony/gsm/SuppServiceNotification;
+Lcom/android/internal/telephony/gsm/UsimPhoneBookManager$File;
+Lcom/android/internal/telephony/gsm/UsimPhoneBookManager$PbrRecord;
+Lcom/android/internal/telephony/HbpcdUtils;
+Lcom/android/internal/telephony/IccPhoneBookInterfaceManager$Request;
+Lcom/android/internal/telephony/IIccPhoneBook$Stub$Proxy;
+Lcom/android/internal/telephony/IIntegerConsumer$Stub;
+Lcom/android/internal/telephony/IIntegerConsumer$Stub$Proxy;
+Lcom/android/internal/telephony/IMms$Stub$Proxy;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$-jFhgP_NotuFSwzjQBXWuvls4x4;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$kF808g2NWzNL8H1SwzDc1FxiQdQ;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$rPjfocpARQ2sab24iic4o3kTTgw;
+Lcom/android/internal/telephony/ims/ImsResolver$8;
+Lcom/android/internal/telephony/ims/ImsServiceFeatureQueryManager$ImsServiceFeatureQuery;
+Lcom/android/internal/telephony/imsphone/ImsExternalConnection;
+Lcom/android/internal/telephony/imsphone/ImsPhone$Cf;
+Lcom/android/internal/telephony/imsphone/ImsPhoneConnection;
+Lcom/android/internal/telephony/imsphone/ImsPhoneMmiCode;
+Lcom/android/internal/telephony/ims/RcsMessageController;
+Lcom/android/internal/telephony/InboundSmsHandler$CarrierServicesSmsFilterCallback;
+Lcom/android/internal/telephony/InboundSmsHandler$SmsBroadcastReceiver;
+Lcom/android/internal/telephony/InboundSmsTracker;
+Lcom/android/internal/telephony/INumberVerificationCallback$Stub;
+Lcom/android/internal/telephony/INumberVerificationCallback$Stub$Proxy;
+Lcom/android/internal/telephony/IPhoneStateListener$Stub$Proxy;
+Lcom/android/internal/telephony/ISetOpportunisticDataCallback$Stub$Proxy;
+Lcom/android/internal/telephony/ISms$Stub$Proxy;
+Lcom/android/internal/telephony/IWapPushManager;
+Lcom/android/internal/telephony/LastCallFailCause;
+Lcom/android/internal/telephony/LinkCapacityEstimate;
+Lcom/android/internal/telephony/metrics/-$$Lambda$TelephonyMetrics$fLmZDbNadlr6LF7zSJ6jCR1AAsk;
+Lcom/android/internal/telephony/metrics/-$$Lambda$TelephonyMetrics$x2dJi76S2YQdpSTfY8RZ8qC_K6g;
+Lcom/android/internal/telephony/metrics/ModemPowerMetrics;
+Lcom/android/internal/telephony/metrics/TelephonyMetrics$1;
+Lcom/android/internal/telephony/MultiSimSettingController$1;
+[Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierAttribute;
+[Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierId;
+[Lcom/android/internal/telephony/nano/TelephonyProto$ActiveSubscriptionInfo;
+Lcom/android/internal/telephony/nano/TelephonyProto$EmergencyNumberInfo;
+Lcom/android/internal/telephony/nano/TelephonyProto$ModemPowerStats;
+[Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession;
+Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession;
+[Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession$Event;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent$OnDemandDataSwitch;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent$RilDeactivateDataCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyHistogram;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyHistogram;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyLog;
+Lcom/android/internal/telephony/nano/TelephonyProto$Time;
+Lcom/android/internal/telephony/NitzStateMachineImpl;
+Lcom/android/internal/telephony/OperatorInfo$State;
+[Lcom/android/internal/telephony/Phone;
+Lcom/android/internal/telephony/PhoneInternalInterface$DialArgs;
+Lcom/android/internal/telephony/PhoneInternalInterface$DialArgs$Builder;
+Lcom/android/internal/telephony/PhoneInternalInterface$SuppService;
+Lcom/android/internal/telephony/PhoneSwitcher$2;
+Lcom/android/internal/telephony/PhoneSwitcher$DefaultNetworkCallback;
+Lcom/android/internal/telephony/PhoneSwitcher$EmergencyOverrideRequest;
+[Lcom/android/internal/telephony/PhoneSwitcher$PhoneState;
+Lcom/android/internal/telephony/protobuf/nano/CodedOutputByteBufferNano$OutOfSpaceException;
+Lcom/android/internal/telephony/protobuf/nano/MessageNanoPrinter;
+Lcom/android/internal/telephony/RadioBugDetector;
+[Lcom/android/internal/telephony/RIL;
+Lcom/android/internal/telephony/sip/SipPhone;
+Lcom/android/internal/telephony/SmsBroadcastUndelivered$SmsReferenceKey;
+Lcom/android/internal/telephony/SmsConstants$MessageClass;
+Lcom/android/internal/telephony/SmsController;
+Lcom/android/internal/telephony/SMSDispatcher$DataSmsSender;
+Lcom/android/internal/telephony/SMSDispatcher$SmsSenderCallback;
+[Lcom/android/internal/telephony/SMSDispatcher$SmsTracker;
+Lcom/android/internal/telephony/SMSDispatcher$SmsTracker;
+Lcom/android/internal/telephony/SMSDispatcher$TextSmsSender;
+Lcom/android/internal/telephony/SmsNumberUtils;
+Lcom/android/internal/telephony/SmsPermissions;
+Lcom/android/internal/telephony/SmsResponse;
+Lcom/android/internal/telephony/TimeServiceHelper;
+Lcom/android/internal/telephony/uicc/CsimFileHandler;
+[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus;
+Lcom/android/internal/telephony/uicc/IccException;
+Lcom/android/internal/telephony/uicc/IccFileNotFound;
+Lcom/android/internal/telephony/uicc/IccFileTypeMismatch;
+Lcom/android/internal/telephony/uicc/IccRefreshResponse;
+Lcom/android/internal/telephony/uicc/IccVmNotSupportedException;
+Lcom/android/internal/telephony/uicc/InstallCarrierAppTrampolineActivity;
+Lcom/android/internal/telephony/uicc/IsimFileHandler;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimDomainLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimImpiLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimImpuLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimIstLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimPcscfLoaded;
+[Lcom/android/internal/telephony/uicc/PlmnActRecord;
+Lcom/android/internal/telephony/uicc/RuimFileHandler;
+Lcom/android/internal/telephony/uicc/RuimRecords;
+Lcom/android/internal/telephony/uicc/SIMFileHandler;
+[Lcom/android/internal/telephony/uicc/UiccCardApplication;
+Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules$TLV;
+Lcom/android/internal/telephony/uicc/UiccPkcs15$FileHandler;
+Lcom/android/internal/telephony/uicc/UiccProfile$5;
+[Lcom/android/internal/telephony/uicc/UiccSlot;
+Lcom/android/internal/telephony/util/SMSDispatcherUtil;
+Lcom/android/internal/telephony/UUSInfo;
+Lcom/android/internal/telephony/VisualVoicemailSmsFilter;
+Lcom/android/internal/textservice/ISpellCheckerService;
+Lcom/android/internal/textservice/ISpellCheckerService$Stub;
+Lcom/android/internal/textservice/ISpellCheckerService$Stub$Proxy;
+Lcom/android/internal/textservice/ISpellCheckerSession$Stub;
+Lcom/android/internal/textservice/ISpellCheckerSessionListener$Stub$Proxy;
+Lcom/android/internal/textservice/ITextServicesSessionListener$Stub$Proxy;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$D1OlZP6xIpu72ypnJd0fzx0wd6I;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$vCLO_0ezRxkpSERUWCFrJ0ph5jg;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$X8irOs5hfloCKy89_l1HRA1QeG0;
+Lcom/android/internal/util/-$$Lambda$eRa1rlfDk6Og2yFeXGHqUGPzRF0;
+Lcom/android/internal/util/-$$Lambda$FunctionalUtils$koCSI8D7Nu5vOJTVTEj0m3leo_U;
+Lcom/android/internal/util/-$$Lambda$grRTg3idX3yJe9Zyx-tmLBiD1DM;
+Lcom/android/internal/util/-$$Lambda$JwOUSWW2-Jzu15y4Kn4JuPh8tWM;
+Lcom/android/internal/util/-$$Lambda$kVylv1rl9MOSbHFZoVyK5dl1kfY;
+Lcom/android/internal/util/-$$Lambda$TCbPpgWlKJUHZgFKCczglAvxLfw;
+Lcom/android/internal/util/ContrastColorUtil;
+Lcom/android/internal/util/ContrastColorUtil$ColorUtilsFromCompat;
+Lcom/android/internal/util/function/HeptPredicate;
+Lcom/android/internal/util/function/HexPredicate;
+Lcom/android/internal/util/function/NonaPredicate;
+Lcom/android/internal/util/function/OctPredicate;
+Lcom/android/internal/util/function/QuadPredicate;
+Lcom/android/internal/util/function/QuintPredicate;
+Lcom/android/internal/util/MessageUtils$DuplicateConstantError;
+Lcom/android/internal/util/ProcFileReader;
+[Lcom/android/internal/util/StateMachine$SmHandler$StateInfo;
+Lcom/android/internal/util/SyncResultReceiver$TimeoutException;
+Lcom/android/internal/util/UserIcons;
+Lcom/android/internal/view/IInputMethod$Stub$Proxy;
+Lcom/android/internal/view/IInputMethodClient$Stub$Proxy;
+Lcom/android/internal/view/IInputSessionCallback$Stub$Proxy;
+Lcom/android/internal/view/InputConnectionWrapper;
+Lcom/android/internal/view/InputConnectionWrapper$InputContextCallback;
+Lcom/android/internal/view/menu/ActionMenuItemView;
+Lcom/android/internal/view/menu/ActionMenuItemView$ActionMenuItemForwardingListener;
+Lcom/android/internal/view/menu/MenuPopupHelper$1;
+Lcom/android/internal/view/menu/MenuView$ItemView;
+Lcom/android/internal/view/OneShotPreDrawListener;
+Lcom/android/internal/view/RotationPolicy$1;
+Lcom/android/internal/widget/ActionBarOverlayLayout;
+Lcom/android/internal/widget/DecorCaptionView;
+Lcom/android/internal/widget/ICheckCredentialProgressCallback$Stub;
+Lcom/android/internal/widget/ICheckCredentialProgressCallback$Stub$Proxy;
+Lcom/android/internal/widget/SwipeDismissLayout;
+Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory;
+Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory$DnsAdapter;
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;
+Lcom/android/org/conscrypt/TrustedCertificateStore;
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;
+Lcom/android/org/conscrypt/TrustManagerImpl;
+[Lcom/android/ph
+[Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo;
+[Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo;
+Lcom/android/server/backup/AccountManagerBackupHelper;
+Lcom/android/server/backup/AccountSyncSettingsBackupHelper;
+Lcom/android/server/backup/NotificationBackupHelper;
+Lcom/android/server/backup/PermissionBackupHelper;
+Lcom/android/server/backup/PreferredActivityBackupHelper;
+Lcom/android/server/backup/ShortcutBackupHelper;
+Lcom/android/server/backup/SliceBackupHelper;
+Lcom/android/server/backup/UsageStatsBackupHelper;
+Lcom/android/server/BootReceiver;
+Lcom/android/server/BootReceiver$1;
+Lcom/android/server/BootReceiver$2;
+[Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$IpConnectivityEvent;
+[Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$Pair;
+Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$Pair;
+Lcom/android/server/sip/SipService$ConnectivityReceiver;
+Lcom/android/server/sip/SipService$MyExecutor;
+Lcom/android/server/sip/SipWakeLock;
+Lcom/android/server/sip/SipWakeupTimer$MyEventComparator;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$AlertReasonCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$ConnectionEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$ConnectToNetworkNotificationAndActionCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$DeviceMobilityStatePnoScanStats;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$GroupEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$HistogramBucketInt32;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$Int32Count;
+Lcom/android/server/wifi/nano/WifiMetricsProto$Int32Count;
+Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$ExperimentProbeCounts;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$LinkProbeFailureReasonCount;
+Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$LinkProbeFailureReasonCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$LinkSpeedCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$NetworkSelectionExperimentDecisions;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$NumConnectableNetworksBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$P2pConnectionEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$PasspointProfileTypeCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$RssiPollCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApConnectedClientsEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApDurationBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApReturnCodeCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$StaEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$HistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$HistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$NanStatusHistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$NanStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiConfigStoreIO$DurationBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiDppLog$DppConfiguratorSuccessStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiDppLog$DppFailureStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiIsUnusableEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiLog$ScanReturnEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiLog$WifiSystemStateEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$HistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$HistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttIndividualStatusHistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttIndividualStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttOverallStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiScoreCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityScoreCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStats;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStatsEntry;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStatsEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiWakeStats$Session;
+Lcom/android/server/wm/nano/WindowManagerProtos$TaskSnapshotProto;
+Lcom/google/android/rappor/Encoder;
+Lcom/google/android/rappor/HmacDrbg;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel$Conversation;
+[Lcom/google/android/textclassifier/ActionsSuggestionsModel$ConversationMessage;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel$ConversationMessage;
+Lcom/google/android/textclassifier/AnnotatorModel;
+Lcom/google/android/textclassifier/LangIdModel;
+[Ljava/lang/Double;
+[Ljava/lang/Runnable;
+[Ljava/lang/Void;
+[Ljava/net/InetAddress;
+Ljava/nio/file/Files$AcceptAllFilter;
+[Ljava/nio/file/OpenOption;
+[[Ljava/security/cert/Certificate;
+[Ljava/security/cert/Certificate;
+[[Ljava/security/cert/X509Certificate;
+[Ljava/security/cert/X509Certificate;
+[Ljava/security/MessageDigest;
+Ljava/security/spec/PSSParameterSpec;
+Ljava/time/-$$Lambda$up1HpCqucM_DXyY-rpDOyCcdmIA;
+[Ljava/util/concurrent/RunnableScheduledFuture;
+[Ljava/util/HashMap;
+Ljava/util/logging/Logger$SystemLoggerHelper;
+Ljava/util/PropertyPermission;
+Ljava/util/stream/-$$Lambda$MatchOps$emK14UX33I4-nqH2o5l7hLEVAy8;
+[Ljavax/net/ssl/TrustManager;
+Llibcore/icu/RelativeDateTimeFormatter;
+Lsun/security/util/SecurityConstants;
+Lsun/util/locale/LocaleMatcher;
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index ec2568d..2dfe019 100644
--- a/config/dirty-image-objects
+++ b/config/dirty-image-objects
@@ -255,7 +255,7 @@
 com.android.internal.policy.DecorView
 com.android.internal.statusbar.IStatusBarService
 com.android.internal.telephony.AppSmsManager
-com.android.internal.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
+android.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
 com.android.internal.telephony.CarrierActionAgent
 com.android.internal.telephony.cat.CatService
 com.android.internal.telephony.cat.IconLoader
diff --git a/config/hiddenapi-greylist-max-o.txt b/config/hiddenapi-greylist-max-o.txt
index d9c1cd0..15026b0 100644
--- a/config/hiddenapi-greylist-max-o.txt
+++ b/config/hiddenapi-greylist-max-o.txt
@@ -3040,7 +3040,6 @@
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->retrieveSecurityLogs(Landroid/content/ComponentName;)Landroid/content/pm/ParceledListSlice;
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAccountManagementDisabled(Landroid/content/ComponentName;Ljava/lang/String;Z)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
-Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setActivePasswordState(Landroid/app/admin/PasswordMetrics;I)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAffiliationIds(Landroid/content/ComponentName;Ljava/util/List;)V
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setAlwaysOnVpnPackage(Landroid/content/ComponentName;Ljava/lang/String;Z)Z
 Landroid/app/admin/IDevicePolicyManager$Stub$Proxy;->setApplicationHidden(Landroid/content/ComponentName;Ljava/lang/String;Ljava/lang/String;Z)Z
@@ -3305,7 +3304,6 @@
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_retrieveSecurityLogs:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAccountManagementDisabled:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setActiveAdmin:I
-Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setActivePasswordState:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAffiliationIds:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setAlwaysOnVpnPackage:I
 Landroid/app/admin/IDevicePolicyManager$Stub;->TRANSACTION_setApplicationHidden:I
@@ -3569,7 +3567,6 @@
 Landroid/app/admin/IDevicePolicyManager;->retrieveSecurityLogs(Landroid/content/ComponentName;)Landroid/content/pm/ParceledListSlice;
 Landroid/app/admin/IDevicePolicyManager;->setAccountManagementDisabled(Landroid/content/ComponentName;Ljava/lang/String;Z)V
 Landroid/app/admin/IDevicePolicyManager;->setActiveAdmin(Landroid/content/ComponentName;ZI)V
-Landroid/app/admin/IDevicePolicyManager;->setActivePasswordState(Landroid/app/admin/PasswordMetrics;I)V
 Landroid/app/admin/IDevicePolicyManager;->setAffiliationIds(Landroid/content/ComponentName;Ljava/util/List;)V
 Landroid/app/admin/IDevicePolicyManager;->setAlwaysOnVpnPackage(Landroid/content/ComponentName;Ljava/lang/String;Z)Z
 Landroid/app/admin/IDevicePolicyManager;->setApplicationHidden(Landroid/content/ComponentName;Ljava/lang/String;Ljava/lang/String;Z)Z
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 9f48f8a..a6e1f0a 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -591,30 +591,6 @@
 Landroid/telephony/mbms/IMbmsStreamingSessionCallback$Stub;-><init>()V
 Landroid/telephony/mbms/IStreamingServiceCallback$Stub;-><init>()V
 Landroid/telephony/mbms/vendor/IMbmsStreamingService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/telephony/mbms/vendor/IMbmsStreamingService;
-Landroid/telephony/SmsCbCmasInfo;->getCategory()I
-Landroid/telephony/SmsCbCmasInfo;->getCertainty()I
-Landroid/telephony/SmsCbCmasInfo;->getMessageClass()I
-Landroid/telephony/SmsCbCmasInfo;->getResponseType()I
-Landroid/telephony/SmsCbCmasInfo;->getSeverity()I
-Landroid/telephony/SmsCbCmasInfo;->getUrgency()I
-Landroid/telephony/SmsCbEtwsInfo;->getWarningType()I
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;)V
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;II)V
-Landroid/telephony/SmsCbLocation;->getCid()I
-Landroid/telephony/SmsCbLocation;->getLac()I
-Landroid/telephony/SmsCbLocation;->getPlmn()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;-><init>(Landroid/os/Parcel;)V
-Landroid/telephony/SmsCbMessage;->getCmasWarningInfo()Landroid/telephony/SmsCbCmasInfo;
-Landroid/telephony/SmsCbMessage;->getEtwsWarningInfo()Landroid/telephony/SmsCbEtwsInfo;
-Landroid/telephony/SmsCbMessage;->getGeographicalScope()I
-Landroid/telephony/SmsCbMessage;->getLanguageCode()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getLocation()Landroid/telephony/SmsCbLocation;
-Landroid/telephony/SmsCbMessage;->getMessageBody()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getMessageFormat()I
-Landroid/telephony/SmsCbMessage;->getSerialNumber()I
-Landroid/telephony/SmsCbMessage;->getServiceCategory()I
-Landroid/telephony/SmsCbMessage;->isCmasMessage()Z
-Landroid/telephony/SmsCbMessage;->isEmergencyMessage()Z
 Landroid/telephony/TelephonyManager$MultiSimVariants;->values()[Landroid/telephony/TelephonyManager$MultiSimVariants;
 Landroid/util/Singleton;-><init>()V
 Landroid/view/accessibility/IAccessibilityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -1174,8 +1150,6 @@
 Lcom/android/internal/statusbar/IStatusBarService$Stub;-><init>()V
 Lcom/android/internal/statusbar/IStatusBarService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/statusbar/IStatusBarService;
 Lcom/android/internal/telecom/ITelecomService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telecom/ITelecomService;
-Lcom/android/internal/telephony/IIccPhoneBook$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Lcom/android/internal/telephony/IIccPhoneBook$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IIccPhoneBook;
 Lcom/android/internal/telephony/IMms$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IMms;
 Lcom/android/internal/telephony/IPhoneStateListener$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IPhoneStateListener;
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -1183,7 +1157,6 @@
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub;->TRANSACTION_getDeviceId:I
 Lcom/android/internal/telephony/ISms$Stub;-><init>()V
 Lcom/android/internal/telephony/ISms$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ISms;
-Lcom/android/internal/telephony/ISub$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Lcom/android/internal/telephony/ISub$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ISub;
 Lcom/android/internal/telephony/ITelephony$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Lcom/android/internal/telephony/ITelephony$Stub$Proxy;->mRemote:Landroid/os/IBinder;
@@ -1207,5 +1180,283 @@
 Lcom/android/server/ResettableTimeout$T;-><init>(Lcom/android/server/ResettableTimeout;)V
 Lcom/google/android/gles_jni/EGLImpl;-><init>()V
 Lcom/google/android/gles_jni/GLImpl;-><init>()V
-Lcom/google/android/mms/pdu/PduParser;->$assertionsDisabled:Z
+Lcom/google/android/mms/ContentType;->getAudioTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getImageTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getVideoTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->isAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isDrmType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isTextType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/InvalidHeaderValueException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>()V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/Throwable;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(I[B)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/Base64;->decodeBase64([B)[B
+Lcom/google/android/mms/pdu/CharacterSets;->getMibEnumValue(Ljava/lang/String;)I
+Lcom/google/android/mms/pdu/CharacterSets;->getMimeName(I)Ljava/lang/String;
+Lcom/google/android/mms/pdu/DeliveryInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/DeliveryInd;->getDate()J
+Lcom/google/android/mms/pdu/DeliveryInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/DeliveryInd;->getStatus()I
+Lcom/google/android/mms/pdu/DeliveryInd;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(I[B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->appendTextString([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->concat([Lcom/google/android/mms/pdu/EncodedStringValue;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->copy(Lcom/google/android/mms/pdu/EncodedStringValue;)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->encodeStrings([Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->extract(Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getCharacterSet()I
+Lcom/google/android/mms/pdu/EncodedStringValue;->getString()Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getTextString()[B
+Lcom/google/android/mms/pdu/EncodedStringValue;->setCharacterSet(I)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->setTextString([B)V
+Lcom/google/android/mms/pdu/GenericPdu;-><init>()V
+Lcom/google/android/mms/pdu/GenericPdu;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/GenericPdu;->getMessageType()I
+Lcom/google/android/mms/pdu/GenericPdu;->getPduHeaders()Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->mPduHeaders:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/GenericPdu;->setMessageType(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>()V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->addTo(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getBody()Lcom/google/android/mms/pdu/PduBody;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getDate()J
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getPriority()I
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setBody(Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setDate(J)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setPriority(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>()V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotificationInd;->getContentClass()I
+Lcom/google/android/mms/pdu/NotificationInd;->getContentLocation()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/NotificationInd;->getExpiry()J
+Lcom/google/android/mms/pdu/NotificationInd;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageClass()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageSize()J
+Lcom/google/android/mms/pdu/NotificationInd;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getTransactionId()[B
+Lcom/google/android/mms/pdu/NotificationInd;->setContentClass(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setExpiry(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(I[BI)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setStatus(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/PduBody;-><init>()V
+Lcom/google/android/mms/pdu/PduBody;->addPart(ILcom/google/android/mms/pdu/PduPart;)V
+Lcom/google/android/mms/pdu/PduBody;->addPart(Lcom/google/android/mms/pdu/PduPart;)Z
+Lcom/google/android/mms/pdu/PduBody;->getPart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentId(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentLocation(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByFileName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartIndex(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduBody;->getPartsNum()I
+Lcom/google/android/mms/pdu/PduBody;->removePart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->copy()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->mark()Lcom/google/android/mms/pdu/PduComposer$PositionMarker;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->newbuf()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->pop()V
+Lcom/google/android/mms/pdu/PduComposer$PositionMarker;->getLength()I
+Lcom/google/android/mms/pdu/PduComposer;-><init>(Landroid/content/Context;Lcom/google/android/mms/pdu/GenericPdu;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendEncodedString(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendHeader(I)I
+Lcom/google/android/mms/pdu/PduComposer;->appendLongInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendOctet(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendShortInteger(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendUintvarInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendValueLength(J)V
+Lcom/google/android/mms/pdu/PduComposer;->arraycopy([BII)V
+Lcom/google/android/mms/pdu/PduComposer;->make()[B
+Lcom/google/android/mms/pdu/PduComposer;->mContentTypeMap:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduComposer;->mMessage:Ljava/io/ByteArrayOutputStream;
+Lcom/google/android/mms/pdu/PduComposer;->mPdu:Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduComposer;->mPduHeader:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/PduComposer;->mPosition:I
+Lcom/google/android/mms/pdu/PduComposer;->mResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduComposer;->mStack:Lcom/google/android/mms/pdu/PduComposer$BufferStack;
+Lcom/google/android/mms/pdu/PduContentTypes;->contentTypes:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduHeaders;-><init>()V
+Lcom/google/android/mms/pdu/PduHeaders;->appendEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValue(I)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValues(I)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getLongInteger(I)J
+Lcom/google/android/mms/pdu/PduHeaders;->getOctet(I)I
+Lcom/google/android/mms/pdu/PduHeaders;->getTextString(I)[B
+Lcom/google/android/mms/pdu/PduHeaders;->setEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->setLongInteger(JI)V
+Lcom/google/android/mms/pdu/PduHeaders;->setOctet(II)V
+Lcom/google/android/mms/pdu/PduParser;-><init>([BZ)V
+Lcom/google/android/mms/pdu/PduParser;->checkPartPosition(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduParser;->log(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduParser;->parse()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduParser;->parseContentType(Ljava/io/ByteArrayInputStream;Ljava/util/HashMap;)[B
+Lcom/google/android/mms/pdu/PduParser;->parsePartHeaders(Ljava/io/ByteArrayInputStream;Lcom/google/android/mms/pdu/PduPart;I)Z
+Lcom/google/android/mms/pdu/PduParser;->parseShortInteger(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseUnsignedInt(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseValueLength(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseWapString(Ljava/io/ByteArrayInputStream;I)[B
+Lcom/google/android/mms/pdu/PduPart;-><init>()V
+Lcom/google/android/mms/pdu/PduPart;->generateLocation()Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPart;->getCharset()I
+Lcom/google/android/mms/pdu/PduPart;->getContentDisposition()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentId()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentLocation()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentTransferEncoding()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentType()[B
+Lcom/google/android/mms/pdu/PduPart;->getData()[B
+Lcom/google/android/mms/pdu/PduPart;->getDataLength()I
+Lcom/google/android/mms/pdu/PduPart;->getDataUri()Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPart;->getFilename()[B
+Lcom/google/android/mms/pdu/PduPart;->getName()[B
+Lcom/google/android/mms/pdu/PduPart;->setCharset(I)V
+Lcom/google/android/mms/pdu/PduPart;->setContentDisposition([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentId([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentTransferEncoding([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentType([B)V
+Lcom/google/android/mms/pdu/PduPart;->setData([B)V
+Lcom/google/android/mms/pdu/PduPart;->setDataUri(Landroid/net/Uri;)V
+Lcom/google/android/mms/pdu/PduPart;->setFilename([B)V
+Lcom/google/android/mms/pdu/PduPart;->setName([B)V
+Lcom/google/android/mms/pdu/PduPersister;->ADDRESS_FIELDS:[I
+Lcom/google/android/mms/pdu/PduPersister;->CHARSET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->ENCODED_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->getByteArrayFromPartColumn(Landroid/database/Cursor;I)[B
+Lcom/google/android/mms/pdu/PduPersister;->getBytes(Ljava/lang/String;)[B
+Lcom/google/android/mms/pdu/PduPersister;->getIntegerFromPartColumn(Landroid/database/Cursor;I)Ljava/lang/Integer;
+Lcom/google/android/mms/pdu/PduPersister;->getPartContentType(Lcom/google/android/mms/pdu/PduPart;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->getPduPersister(Landroid/content/Context;)Lcom/google/android/mms/pdu/PduPersister;
+Lcom/google/android/mms/pdu/PduPersister;->getPendingMessages(J)Landroid/database/Cursor;
+Lcom/google/android/mms/pdu/PduPersister;->load(Landroid/net/Uri;)Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduPersister;->loadRecipients(ILjava/util/HashSet;Ljava/util/HashMap;Z)V
+Lcom/google/android/mms/pdu/PduPersister;->LONG_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->mContentResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduPersister;->mContext:Landroid/content/Context;
+Lcom/google/android/mms/pdu/PduPersister;->MESSAGE_BOX_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->move(Landroid/net/Uri;Landroid/net/Uri;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->mTelephonyManager:Landroid/telephony/TelephonyManager;
+Lcom/google/android/mms/pdu/PduPersister;->OCTET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->PART_PROJECTION:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->PDU_CACHE_INSTANCE:Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/pdu/PduPersister;->persist(Lcom/google/android/mms/pdu/GenericPdu;Landroid/net/Uri;ZZLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->persistAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->persistPart(Lcom/google/android/mms/pdu/PduPart;JLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->TEXT_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->toIsoString([B)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->updateAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateHeaders(Landroid/net/Uri;Lcom/google/android/mms/pdu/SendReq;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateParts(Landroid/net/Uri;Lcom/google/android/mms/pdu/PduBody;Ljava/util/HashMap;)V
+Lcom/google/android/mms/pdu/QuotedPrintable;->decodeQuotedPrintable([B)[B
+Lcom/google/android/mms/pdu/ReadOrigInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadOrigInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadOrigInd;->getReadStatus()I
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/EncodedStringValue;[BII[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadRecInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadRecInd;->setDate(J)V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>()V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getContentType()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageClass()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getReadReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveStatus()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveText()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->setContentType([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageId([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setReadReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveStatus(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveText(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/SendConf;-><init>()V
+Lcom/google/android/mms/pdu/SendConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/SendConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/SendConf;->getResponseStatus()I
+Lcom/google/android/mms/pdu/SendConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;-><init>()V
+Lcom/google/android/mms/pdu/SendReq;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/SendReq;->addBcc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->getBcc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getContentType()[B
+Lcom/google/android/mms/pdu/SendReq;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/SendReq;->getExpiry()J
+Lcom/google/android/mms/pdu/SendReq;->getMessageClass()[B
+Lcom/google/android/mms/pdu/SendReq;->getMessageSize()J
+Lcom/google/android/mms/pdu/SendReq;->getReadReport()I
+Lcom/google/android/mms/pdu/SendReq;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;->setBcc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setCc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setContentType([B)V
+Lcom/google/android/mms/pdu/SendReq;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setExpiry(J)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/SendReq;->setReadReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setTo([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setTransactionId([B)V
+Lcom/google/android/mms/util/AbstractCache;-><init>()V
+Lcom/google/android/mms/util/AbstractCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purgeAll()V
+Lcom/google/android/mms/util/AbstractCache;->put(Ljava/lang/Object;Ljava/lang/Object;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->isDrmConvertNeeded(Ljava/lang/String;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->modifyDrmFwLockFileExtension(Ljava/lang/String;)Ljava/lang/String;
+Lcom/google/android/mms/util/DrmConvertSession;->close(Ljava/lang/String;)I
+Lcom/google/android/mms/util/DrmConvertSession;->convert([BI)[B
+Lcom/google/android/mms/util/DrmConvertSession;->open(Landroid/content/Context;Ljava/lang/String;)Lcom/google/android/mms/util/DrmConvertSession;
+Lcom/google/android/mms/util/PduCache;-><init>()V
+Lcom/google/android/mms/util/PduCache;->getInstance()Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/util/PduCache;->isUpdating(Landroid/net/Uri;)Z
+Lcom/google/android/mms/util/PduCache;->purge(Landroid/net/Uri;)Lcom/google/android/mms/util/PduCacheEntry;
+Lcom/google/android/mms/util/PduCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/PduCache;->purgeAll()V
+Lcom/google/android/mms/util/PduCacheEntry;-><init>(Lcom/google/android/mms/pdu/GenericPdu;IJ)V
+Lcom/google/android/mms/util/PduCacheEntry;->getMessageBox()I
+Lcom/google/android/mms/util/PduCacheEntry;->getPdu()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/util/PduCacheEntry;->getThreadId()J
+Lcom/google/android/mms/util/SqliteWrapper;->checkSQLiteException(Landroid/content/Context;Landroid/database/sqlite/SQLiteException;)V
+Lcom/google/android/mms/util/SqliteWrapper;->delete(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I
+Lcom/google/android/mms/util/SqliteWrapper;->insert(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri;
+Lcom/google/android/mms/util/SqliteWrapper;->query(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
+Lcom/google/android/mms/util/SqliteWrapper;->requery(Landroid/content/Context;Landroid/database/Cursor;)Z
+Lcom/google/android/mms/util/SqliteWrapper;->update(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I
 Lcom/google/android/util/AbstractMessageParser$Token$Type;->values()[Lcom/google/android/util/AbstractMessageParser$Token$Type;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 778a4d7..8d91144 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4698,7 +4698,7 @@
 com.android.internal.telephony.CallManager
 com.android.internal.telephony.CallStateException
 com.android.internal.telephony.CallTracker
-com.android.internal.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
+android.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
 com.android.internal.telephony.CarrierActionAgent$1
 com.android.internal.telephony.CarrierActionAgent
 com.android.internal.telephony.CarrierAppUtils
@@ -4838,7 +4838,6 @@
 com.android.internal.telephony.PhoneConstants$DataState
 com.android.internal.telephony.PhoneConstants$State
 com.android.internal.telephony.PhoneFactory
-com.android.internal.telephony.PhoneInternalInterface$DataActivityState
 com.android.internal.telephony.PhoneInternalInterface
 com.android.internal.telephony.PhoneNotifier
 com.android.internal.telephony.PhoneStateIntentReceiver
diff --git a/config/preloaded-classes-blacklist b/config/preloaded-classes-blacklist
index f05edee..353f786 100644
--- a/config/preloaded-classes-blacklist
+++ b/config/preloaded-classes-blacklist
@@ -4,4 +4,3 @@
 android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
 android.widget.Magnifier
 sun.nio.fs.UnixChannelFactory
-android.permission.PermissionManager
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
new file mode 100644
index 0000000..58a9b64
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.accessibilityservice;
+
+parcelable AccessibilityGestureEvent;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
new file mode 100644
index 0000000..b540175
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.accessibilityservice;
+
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the gesture event including gesture id and which display it happens
+ * on.
+ * <p>
+ * <strong>Note:</strong> Accessibility services setting the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag can receive gestures.
+ *
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
+ */
+
+public final class AccessibilityGestureEvent implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = { "GESTURE_" }, value = {
+            GESTURE_SWIPE_UP,
+            GESTURE_SWIPE_UP_AND_LEFT,
+            GESTURE_SWIPE_UP_AND_DOWN,
+            GESTURE_SWIPE_UP_AND_RIGHT,
+            GESTURE_SWIPE_DOWN,
+            GESTURE_SWIPE_DOWN_AND_LEFT,
+            GESTURE_SWIPE_DOWN_AND_UP,
+            GESTURE_SWIPE_DOWN_AND_RIGHT,
+            GESTURE_SWIPE_LEFT,
+            GESTURE_SWIPE_LEFT_AND_UP,
+            GESTURE_SWIPE_LEFT_AND_RIGHT,
+            GESTURE_SWIPE_LEFT_AND_DOWN,
+            GESTURE_SWIPE_RIGHT,
+            GESTURE_SWIPE_RIGHT_AND_UP,
+            GESTURE_SWIPE_RIGHT_AND_LEFT,
+            GESTURE_SWIPE_RIGHT_AND_DOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GestureType {}
+
+    @GestureType
+    private final int mGestureId;
+    private final int mDisplayId;
+
+    /** @hide */
+    @TestApi
+    public AccessibilityGestureEvent(int gestureId, int displayId) {
+        mGestureId = gestureId;
+        mDisplayId = displayId;
+    }
+
+    private AccessibilityGestureEvent(@NonNull Parcel parcel) {
+        mGestureId = parcel.readInt();
+        mDisplayId = parcel.readInt();
+    }
+
+    /**
+     * Returns the display id of the received-gesture display, for use with
+     * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+     *
+     * @return the display id.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Returns performed gesture id.
+     *
+     * @return the performed gesture id.
+     *
+     */
+    @GestureType public int getGestureId() {
+        return mGestureId;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
+        stringBuilder.append("gestureId: ").append(mGestureId);
+        stringBuilder.append(", ");
+        stringBuilder.append("displayId: ").append(mDisplayId);
+        stringBuilder.append(']');
+        return stringBuilder.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mGestureId);
+        parcel.writeInt(mDisplayId);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+            new Parcelable.Creator<AccessibilityGestureEvent>() {
+        public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+            return new AccessibilityGestureEvent(parcel);
+        }
+
+        public AccessibilityGestureEvent[] newArray(int size) {
+            return new AccessibilityGestureEvent[size];
+        }
+    };
+
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl b/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
deleted file mode 100644
index 2539051..0000000
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
+++ /dev/null
@@ -1,19 +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 android.accessibilityservice;
-
-parcelable AccessibilityGestureInfo;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java b/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
deleted file mode 100644
index dc50a4c..0000000
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
+++ /dev/null
@@ -1,155 +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 android.accessibilityservice;
-
-
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
-import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * This class describes the gesture information including gesture id and which display it happens
- * on.
- * <p>
- * <strong>Note:</strong> Accessibility services setting the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
- * flag can receive gestures.
- *
- * @see AccessibilityService#onGesture(AccessibilityGestureInfo)
- */
-
-public final class AccessibilityGestureInfo implements Parcelable {
-
-    /** @hide */
-    @IntDef(prefix = { "GESTURE_" }, value = {
-            GESTURE_SWIPE_UP,
-            GESTURE_SWIPE_UP_AND_LEFT,
-            GESTURE_SWIPE_UP_AND_DOWN,
-            GESTURE_SWIPE_UP_AND_RIGHT,
-            GESTURE_SWIPE_DOWN,
-            GESTURE_SWIPE_DOWN_AND_LEFT,
-            GESTURE_SWIPE_DOWN_AND_UP,
-            GESTURE_SWIPE_DOWN_AND_RIGHT,
-            GESTURE_SWIPE_LEFT,
-            GESTURE_SWIPE_LEFT_AND_UP,
-            GESTURE_SWIPE_LEFT_AND_RIGHT,
-            GESTURE_SWIPE_LEFT_AND_DOWN,
-            GESTURE_SWIPE_RIGHT,
-            GESTURE_SWIPE_RIGHT_AND_UP,
-            GESTURE_SWIPE_RIGHT_AND_LEFT,
-            GESTURE_SWIPE_RIGHT_AND_DOWN
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface GestureType {}
-
-    @GestureType
-    private final int mGestureId;
-    private final int mDisplayId;
-
-    /** @hide */
-    @TestApi
-    public AccessibilityGestureInfo(int gestureId, int displayId) {
-        mGestureId = gestureId;
-        mDisplayId = displayId;
-    }
-
-    private AccessibilityGestureInfo(@NonNull Parcel parcel) {
-        mGestureId = parcel.readInt();
-        mDisplayId = parcel.readInt();
-    }
-
-    /**
-     * Returns the display id of the received-gesture display, for use with
-     * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
-     *
-     * @return the display id.
-     */
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    /**
-     * Returns performed gesture id.
-     *
-     * @return the performed gesture id.
-     *
-     */
-    @GestureType public int getGestureId() {
-        return mGestureId;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureInfo[");
-        stringBuilder.append("gestureId: ").append(mGestureId);
-        stringBuilder.append(", ");
-        stringBuilder.append("displayId: ").append(mDisplayId);
-        stringBuilder.append(']');
-        return stringBuilder.toString();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(mGestureId);
-        parcel.writeInt(mDisplayId);
-    }
-
-    /**
-     * @see Parcelable.Creator
-     */
-    public static final @NonNull Parcelable.Creator<AccessibilityGestureInfo> CREATOR =
-            new Parcelable.Creator<AccessibilityGestureInfo>() {
-        public AccessibilityGestureInfo createFromParcel(Parcel parcel) {
-            return new AccessibilityGestureInfo(parcel);
-        }
-
-        public AccessibilityGestureInfo[] newArray(int size) {
-            return new AccessibilityGestureInfo[size];
-        }
-    };
-
-}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a8daf91..d3c274f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -382,7 +382,7 @@
         void onServiceConnected();
         void init(int connectionId, IBinder windowToken);
         /** The detected gesture information for different displays */
-        boolean onGesture(AccessibilityGestureInfo gestureInfo);
+        boolean onGesture(AccessibilityGestureEvent gestureInfo);
         boolean onKeyEvent(KeyEvent event);
         /** Magnification changed callbacks for different displays */
         void onMagnificationChanged(int displayId, @NonNull Region region,
@@ -517,7 +517,7 @@
     }
 
     /**
-     * Called by {@link #onGesture(AccessibilityGestureInfo)} when the user performs a specific
+     * Called by {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
      * gesture on the default display.
      *
      * <strong>Note:</strong> To receive gestures an accessibility service must
@@ -528,7 +528,7 @@
      * @param gestureId The unique id of the performed gesture.
      *
      * @return Whether the gesture was handled.
-     * @deprecated Override {@link #onGesture(AccessibilityGestureInfo)} instead.
+     * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
      *
      * @see #GESTURE_SWIPE_UP
      * @see #GESTURE_SWIPE_UP_AND_LEFT
@@ -564,14 +564,14 @@
      * <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
      * touch screen is default display.
      *
-     * @param gestureInfo The information of gesture.
+     * @param gestureEvent The information of gesture.
      *
      * @return Whether the gesture was handled.
      *
      */
-    public boolean onGesture(@NonNull AccessibilityGestureInfo gestureInfo) {
-        if (gestureInfo.getDisplayId() == Display.DEFAULT_DISPLAY) {
-            onGesture(gestureInfo.getGestureId());
+    public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+        if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            onGesture(gestureEvent.getGestureId());
         }
         return false;
     }
@@ -1725,8 +1725,8 @@
             }
 
             @Override
-            public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
-                return AccessibilityService.this.onGesture(gestureInfo);
+            public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+                return AccessibilityService.this.onGesture(gestureEvent);
             }
 
             @Override
@@ -1826,7 +1826,7 @@
         }
 
         @Override
-        public void onGesture(AccessibilityGestureInfo gestureInfo) {
+        public void onGesture(AccessibilityGestureEvent gestureInfo) {
             Message message = mCaller.obtainMessageO(DO_ON_GESTURE, gestureInfo);
             mCaller.sendMessage(message);
         }
@@ -1942,7 +1942,7 @@
 
                 case DO_ON_GESTURE: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
-                        mCallback.onGesture((AccessibilityGestureInfo) message.obj);
+                        mCallback.onGesture((AccessibilityGestureEvent) message.obj);
                     }
                 } return;
 
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index e0d5e44..8ec3aea 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -20,7 +20,7 @@
 import android.graphics.Region;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.view.KeyEvent;
 
 /**
@@ -36,7 +36,7 @@
 
     void onInterrupt();
 
-    void onGesture(in AccessibilityGestureInfo gestureInfo);
+    void onGesture(in AccessibilityGestureEvent gestureEvent);
 
     void clearAccessibilityCache();
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 15c6425..bf7d632 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -146,6 +146,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -945,6 +946,10 @@
     /** @hide */
     boolean mEnterAnimationComplete;
 
+    /** Track last dispatched multi-window and PiP mode to client, internal debug purpose **/
+    private Boolean mLastDispatchedIsInMultiWindowMode;
+    private Boolean mLastDispatchedIsInPictureInPictureMode;
+
     private static native String getDlWarning();
 
     /** Return the intent that started this activity. */
@@ -3646,6 +3651,22 @@
         return false;
     }
 
+    private static final class RequestFinishCallback extends IRequestFinishCallback.Stub {
+        private final WeakReference<Activity> mActivityRef;
+
+        RequestFinishCallback(WeakReference<Activity> activityRef) {
+            mActivityRef = activityRef;
+        }
+
+        @Override
+        public void requestFinish() {
+            Activity activity = mActivityRef.get();
+            if (activity != null) {
+                activity.mHandler.post(activity::finishAfterTransition);
+            }
+        }
+    }
+
     /**
      * Called when the activity has detected the user's press of the back
      * key.  The default implementation simply finishes the current activity,
@@ -3671,11 +3692,7 @@
             // while at the root of the task. This call allows ActivityTaskManager
             // to intercept or defer finishing.
             ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
-                    new IRequestFinishCallback.Stub() {
-                        public void requestFinish() {
-                            mHandler.post(() -> finishAfterTransition());
-                        }
-                    });
+                    new RequestFinishCallback(new WeakReference<>(this)));
         } catch (RemoteException e) {
             finishAfterTransition();
         }
@@ -6990,6 +7007,10 @@
                 writer.print(mResumed); writer.print(" mStopped=");
                 writer.print(mStopped); writer.print(" mFinished=");
                 writer.println(mFinished);
+        writer.print(innerPrefix); writer.print("mLastDispatchedIsInMultiWindowMode=");
+                writer.print(mLastDispatchedIsInMultiWindowMode);
+                writer.print(" mLastDispatchedIsInPictureInPictureMode=");
+                writer.println(mLastDispatchedIsInPictureInPictureMode);
         writer.print(innerPrefix); writer.print("mChangingConfigurations=");
                 writer.println(mChangingConfigurations);
         writer.print(innerPrefix); writer.print("mCurrentConfig=");
@@ -8071,6 +8092,7 @@
         if (mWindow != null) {
             mWindow.onMultiWindowModeChanged();
         }
+        mLastDispatchedIsInMultiWindowMode = isInMultiWindowMode;
         onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
@@ -8083,6 +8105,7 @@
         if (mWindow != null) {
             mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
         }
+        mLastDispatchedIsInPictureInPictureMode = isInPictureInPictureMode;
         onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
     }
 
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 1fd7e52..92aabb5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -30,7 +31,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.TransactionTooLargeException;
-import android.view.RemoteAnimationAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -51,12 +51,12 @@
     /**
      * Verify that calling app has access to the given provider.
      */
-    public abstract String checkContentProviderAccess(String authority, int userId);
+    public abstract String checkContentProviderAccess(String authority, @UserIdInt int userId);
 
     /**
      * Verify that calling UID has access to the given provider.
      */
-    public abstract int checkContentProviderUriPermission(Uri uri, int userId,
+    public abstract int checkContentProviderUriPermission(Uri uri, @UserIdInt int userId,
             int callingUid, int modeFlags);
 
     // Called by the power manager.
@@ -71,7 +71,7 @@
     /**
      * Kill foreground apps from the specified user.
      */
-    public abstract void killForegroundAppsForUser(int userHandle);
+    public abstract void killForegroundAppsForUser(@UserIdInt int userId);
 
     /**
      *  Sets how long a {@link PendingIntent} can be temporarily whitelist to by bypass restrictions
@@ -174,7 +174,7 @@
      * Checks to see if the calling pid is allowed to handle the user. Returns adjusted user id as
      * needed.
      */
-    public abstract int handleIncomingUser(int callingPid, int callingUid, int userId,
+    public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId,
             boolean allowAll, int allowMode, String name, String callerPackage);
 
     /** Checks if the calling binder pid as the permission. */
@@ -184,7 +184,7 @@
     public abstract int getCurrentUserId();
 
     /** Returns true if the user is running. */
-    public abstract boolean isUserRunning(int userId, int flags);
+    public abstract boolean isUserRunning(@UserIdInt int userId, int flags);
 
     /** Trims memory usage in the system by removing/stopping unused application processes. */
     public abstract void trimApplications();
@@ -211,7 +211,7 @@
      * @param started
      */
     public abstract void updateBatteryStats(
-            ComponentName activity, int uid, int userId, boolean resumed);
+            ComponentName activity, int uid, @UserIdInt int userId, boolean resumed);
 
     /**
      * Update UsageStats of the activity.
@@ -222,23 +222,23 @@
      * @param taskRoot TaskRecord's root
      */
     public abstract void updateActivityUsageStats(
-            ComponentName activity, int userId, int event, IBinder appToken,
+            ComponentName activity, @UserIdInt int userId, int event, IBinder appToken,
             ComponentName taskRoot);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
-    public abstract void sendForegroundProfileChanged(int userId);
+    public abstract void sendForegroundProfileChanged(@UserIdInt int userId);
 
     /**
      * Returns whether the given user requires credential entry at this time. This is used to
      * intercept activity launches for work apps when the Work Challenge is present.
      */
-    public abstract boolean shouldConfirmCredentials(int userId);
+    public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
 
     public abstract int[] getCurrentProfileIds();
     public abstract UserInfo getCurrentUser();
-    public abstract void ensureNotSpecialUser(int userId);
-    public abstract boolean isCurrentProfile(int userId);
-    public abstract boolean hasStartedUserState(int userId);
+    public abstract void ensureNotSpecialUser(@UserIdInt int userId);
+    public abstract boolean isCurrentProfile(@UserIdInt int userId);
+    public abstract boolean hasStartedUserState(@UserIdInt int userId);
     public abstract void finishUserSwitch(Object uss);
 
     /** Schedule the execution of all pending app GCs. */
@@ -261,15 +261,16 @@
     public abstract int broadcastIntentInPackage(String packageName, int uid, int realCallingUid,
             int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras, String requiredPermission,
-            Bundle bOptions, boolean serialized, boolean sticky, int userId,
+            Bundle bOptions, boolean serialized, boolean sticky, @UserIdInt int userId,
             boolean allowBackgroundActivityStarts);
     public abstract ComponentName startServiceInPackage(int uid, Intent service,
-            String resolvedType, boolean fgRequired, String callingPackage, int userId,
+            String resolvedType, boolean fgRequired, String callingPackage, @UserIdInt int userId,
             boolean allowBackgroundActivityStarts) throws TransactionTooLargeException;
 
     public abstract void disconnectActivityFromServices(Object connectionHolder, Object conns);
-    public abstract void cleanUpServices(int userId, ComponentName component, Intent baseIntent);
-    public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId);
+    public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
+            Intent baseIntent);
+    public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, @UserIdInt int userId);
     public abstract void ensureBootCompleted();
     public abstract void updateOomLevelsForDisplay(int displayId);
     public abstract boolean isActivityStartsLoggingEnabled();
@@ -328,7 +329,7 @@
     public abstract boolean isAppBad(ApplicationInfo info);
 
     /** Remove pending backup for the given userId. */
-    public abstract void clearPendingBackup(int userId);
+    public abstract void clearPendingBackup(@UserIdInt int userId);
 
     /**
      * When power button is very long pressed, call this interface to do some pre-shutdown work
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index b8d9575..6772884 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -829,6 +829,17 @@
         return exit;
     }
 
+    /**
+     * Needed for virtual devices because they can be slow enough that the 1 second timeout
+     * triggers when it doesn't on normal devices.
+     *
+     * @hide
+     */
+    @TestApi
+    public static void setExitTransitionTimeout(long timeoutMillis) {
+        ExitTransitionCoordinator.sMaxWaitMillis = timeoutMillis;
+    }
+
     /** @hide */
     static ActivityOptions makeSceneTransitionAnimation(Activity activity,
             ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 4ef554d..ac8c9f4 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -202,21 +202,6 @@
     }
 
     /**
-     * Resizes the input stack id to the given bounds.
-     * @param stackId Id of the stack to resize.
-     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     */
-    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public void resizeStack(int stackId, Rect bounds) throws SecurityException {
-        try {
-            getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
-                    false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Removes stacks in the windowing modes from the system if they are of activity type
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
@@ -362,10 +347,13 @@
      * @param animate Whether we should play an animation for resizing stack.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public void resizeStack(int stackId, Rect bounds, boolean animate) {
+    public void resizePinnedStack(int stackId, Rect bounds, boolean animate) {
         try {
-            getService().resizeStack(stackId, bounds, false, false, animate /* animate */,
-                    -1 /* animationDuration */);
+            if (animate) {
+                getService().animateResizePinnedStack(stackId, bounds, -1 /* animationDuration */);
+            } else {
+                getService().resizePinnedStack(bounds, null /* tempPinnedTaskBounds */);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a6784780..1cc8499 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3983,24 +3983,14 @@
                 data.info.applicationInfo, data.compatInfo);
         Service service = null;
         try {
-            java.lang.ClassLoader cl = packageInfo.getClassLoader();
-            service = packageInfo.getAppFactory()
-                    .instantiateService(cl, data.info.name, data.intent);
-        } catch (Exception e) {
-            if (!mInstrumentation.onException(service, e)) {
-                throw new RuntimeException(
-                    "Unable to instantiate service " + data.info.name
-                    + ": " + e.toString(), e);
-            }
-        }
-
-        try {
             if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
 
             ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
-            context.setOuterContext(service);
-
             Application app = packageInfo.makeApplication(false, mInstrumentation);
+            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            service = packageInfo.getAppFactory()
+                    .instantiateService(cl, data.info.name, data.intent);
+            context.setOuterContext(service);
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
             service.onCreate();
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 3a95839..0f9a6e6 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -197,13 +197,21 @@
         mHasExited = false;
         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
-        if (mEnterActivityOptions.isReturning()) {
+        final boolean isReturning = mEnterActivityOptions.isReturning();
+        if (isReturning) {
             restoreExitedViews();
             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
         }
         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
-                mEnterActivityOptions.isCrossTask());
+                mEnterActivityOptions.isCrossTask(),
+                () -> {
+                    if (isReturning) {
+                        // once it is done transitioning, we don't need the coordinator --
+                        // if we kept it around, it could leak Views
+                        mEnterTransitionCoordinator = null;
+                    }
+                });
         if (mEnterActivityOptions.isCrossTask()) {
             mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
             mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 17697db..08c97eb 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -19,6 +19,9 @@
 import android.compat.Compatibility;
 import android.os.Process;
 import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.internal.compat.ChangeReporter;
 
 import java.util.Arrays;
 
@@ -28,10 +31,10 @@
  * @hide
  */
 public final class AppCompatCallbacks extends Compatibility.Callbacks {
-
     private static final String TAG = "Compatibility";
 
     private final long[] mDisabledChanges;
+    private final ChangeReporter mChangeReporter;
 
     /**
      * Install this class into the current process.
@@ -45,20 +48,29 @@
     private AppCompatCallbacks(long[] disabledChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
         Arrays.sort(mDisabledChanges);
+        mChangeReporter = new ChangeReporter();
     }
 
     protected void reportChange(long changeId) {
-        Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
-        // TODO log via StatsLog
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     protected boolean isChangeEnabled(long changeId) {
         if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
             // Not present in the disabled array
-            reportChange(changeId);
+            reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
+    private void reportChange(long changeId, int state) {
+        int uid = Process.myUid();
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+    }
+
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 34045c9..1649e8b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -28,13 +28,19 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.usage.UsageStatsManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
+import android.database.DatabaseUtils;
 import android.media.AudioAttributes.AttributeUsage;
 import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
@@ -49,9 +55,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.ZygoteInit;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
@@ -59,10 +68,12 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -118,6 +129,12 @@
     private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
             new ArrayMap<>();
 
+    private static final Object sLock = new Object();
+
+    /** Current {@link AppOpsCollector}. Change via {@link #setNotedAppOpsCollector} */
+    @GuardedBy("sLock")
+    private static @Nullable AppOpsCollector sNotedAppOpsCollector;
+
     static IBinder sToken;
 
     /** @hide */
@@ -836,12 +853,14 @@
     public static final int OP_ACCESS_ACCESSIBILITY = 88;
     /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
     public static final int OP_READ_DEVICE_IDENTIFIERS = 89;
+    /** @hide Read location metadata from media */
+    public static final int OP_ACCESS_MEDIA_LOCATION = 90;
     /** @hide Query all apps on device, regardless of declarations in the calling app manifest */
-    public static final int OP_QUERY_ALL_PACKAGES = 90;
+    public static final int OP_QUERY_ALL_PACKAGES = 91;
 
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 91;
+    public static final int _NUM_OP = 92;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1117,6 +1136,8 @@
     /** @hide Has a legacy (non-isolated) view of storage. */
     @SystemApi @TestApi
     public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    /** @hide Read location metadata from media */
+    public static final String OPSTR_ACCESS_MEDIA_LOCATION = "android:access_media_location";
 
     /** @hide Interact with accessibility. */
     @SystemApi
@@ -1126,6 +1147,22 @@
     /** @hide Query all packages on device */
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
 
+
+    /** {@link #sAppOpsToNote} not initialized yet for this op */
+    private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
+    /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
+    private static final byte SHOULD_NOT_COLLECT_NOTE_OP = 1;
+    /** Should collect noting of this app-op in {@link #sAppOpsToNote} */
+    private static final byte SHOULD_COLLECT_NOTE_OP = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SHOULD_" }, value = {
+            SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED,
+            SHOULD_NOT_COLLECT_NOTE_OP,
+            SHOULD_COLLECT_NOTE_OP
+    })
+    private @interface ShouldCollectNoteOp {}
+
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
     private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -1147,6 +1184,7 @@
             // Storage
             OP_READ_EXTERNAL_STORAGE,
             OP_WRITE_EXTERNAL_STORAGE,
+            OP_ACCESS_MEDIA_LOCATION,
             // Location
             OP_COARSE_LOCATION,
             OP_FINE_LOCATION,
@@ -1185,6 +1223,9 @@
             OP_REQUEST_INSTALL_PACKAGES,
             OP_START_FOREGROUND,
             OP_SMS_FINANCIAL_TRANSACTIONS,
+            OP_MANAGE_IPSEC_TUNNELS,
+            OP_GET_USAGE_STATS,
+            OP_INSTANT_APP_START_FOREGROUND
     };
 
     /**
@@ -1286,6 +1327,7 @@
             OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
             OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
             OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
+            OP_ACCESS_MEDIA_LOCATION,           // ACCESS_MEDIA_LOCATION
             OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
     };
 
@@ -1383,6 +1425,7 @@
             OPSTR_LEGACY_STORAGE,
             OPSTR_ACCESS_ACCESSIBILITY,
             OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_ACCESS_MEDIA_LOCATION,
             OPSTR_QUERY_ALL_PACKAGES,
     };
 
@@ -1481,6 +1524,7 @@
             "LEGACY_STORAGE",
             "ACCESS_ACCESSIBILITY",
             "READ_DEVICE_IDENTIFIERS",
+            "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
     };
 
@@ -1565,7 +1609,7 @@
             Manifest.permission.REQUEST_DELETE_PACKAGES,
             Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
             Manifest.permission.ACCEPT_HANDOVER,
-            null, // no permission for OP_MANAGE_IPSEC_TUNNELS
+            Manifest.permission.MANAGE_IPSEC_TUNNELS,
             Manifest.permission.FOREGROUND_SERVICE,
             null, // no permission for OP_BLUETOOTH_SCAN
             Manifest.permission.USE_BIOMETRIC,
@@ -1580,6 +1624,7 @@
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
             null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
+            Manifest.permission.ACCESS_MEDIA_LOCATION,
             null, // no permission for OP_QUERY_ALL_PACKAGES
     };
 
@@ -1679,6 +1724,7 @@
             null, // LEGACY_STORAGE
             null, // ACCESS_ACCESSIBILITY
             null, // READ_DEVICE_IDENTIFIERS
+            null, // ACCESS_MEDIA_LOCATION
             null, // QUERY_ALL_PACKAGES
     };
 
@@ -1777,6 +1823,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1874,6 +1921,7 @@
             AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
             AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
             AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
+            AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
             AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
     };
 
@@ -1975,6 +2023,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1988,6 +2037,27 @@
      */
     private static HashMap<String, Integer> sPermToOp = new HashMap<>();
 
+    /**
+     * Set to the uid of the caller if this thread is currently executing a two-way binder
+     * transaction. Not set if this thread is currently not executing a two way binder transaction.
+     *
+     * @see #startNotedAppOpsCollection
+     * @see #markAppOpNoted
+     */
+    private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
+
+    /**
+     * If a thread is currently executing a two-way binder transaction, this stores the op-codes of
+     * the app-ops that were noted during this transaction.
+     *
+     * @see #markAppOpNoted
+     */
+    private static final ThreadLocal<long[]> sAppOpsNotedInThisBinderTransaction =
+            new ThreadLocal<>();
+
+    /** Whether noting for an appop should be collected */
+    private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
+
     static {
         if (sOpToSwitch.length != _NUM_OP) {
             throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
@@ -2031,6 +2101,12 @@
                 sPermToOp.put(sOpPerms[op], op);
             }
         }
+
+        if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
+            // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
+            // two longs
+            throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
+        }
     }
 
     /** @hide */
@@ -2608,7 +2684,7 @@
          * @return The proxy UID.
          */
         public int getProxyUid() {
-            return (int) findFirstNonNegativeForFlagsInStates(mDurations,
+            return (int) findFirstNonNegativeForFlagsInStates(mProxyUids,
                     MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
         }
 
@@ -2630,7 +2706,7 @@
          * @return The proxy UID.
          */
         public int getProxyUid(@UidState int uidState, @OpFlags int flags) {
-            return (int) findFirstNonNegativeForFlagsInStates(mDurations,
+            return (int) findFirstNonNegativeForFlagsInStates(mProxyUids,
                     uidState, uidState, flags);
         }
 
@@ -3220,7 +3296,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) {
                 return true;
             }
@@ -3251,6 +3327,7 @@
             return result;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return getClass().getSimpleName() + "[from:"
@@ -3486,7 +3563,7 @@
         };
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) {
                 return true;
             }
@@ -3718,7 +3795,7 @@
         };
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) {
                 return true;
             }
@@ -4072,7 +4149,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) {
                 return true;
             }
@@ -4190,8 +4267,8 @@
      * end UID states.
      *
      * @param counts The data array.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
      * @return The sum.
      */
@@ -4220,13 +4297,13 @@
      * end UID states.
      *
      * @param counts The data array.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
      * @return The non-negative value or -1.
      */
     private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts,
-            @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) {
+            @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
         if (counts == null) {
             return -1;
         }
@@ -4252,14 +4329,14 @@
      * end UID states.
      *
      * @param counts The data array.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
      * @return The non-negative value or -1.
      */
     private static @Nullable String findFirstNonNullForFlagsInStates(
-            @Nullable LongSparseArray<String> counts, @OpFlags int flags,
-            @UidState int beginUidState, @UidState int endUidState) {
+            @Nullable LongSparseArray<String> counts, @UidState int beginUidState,
+            @UidState int endUidState, @OpFlags int flags) {
         if (counts == null) {
             return null;
         }
@@ -4350,8 +4427,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
@@ -4374,8 +4451,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
@@ -4396,8 +4473,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param uid The uid of the application of interest.
      * @param packageName The name of the application of interest.
@@ -4429,8 +4506,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param uid The uid of the application of interest.
      * @param packageName The name of the application of interest.
@@ -4820,7 +4897,7 @@
      *
      * @see #isOperationActive
      * @see #stopWatchingActive
-     * @see #startOp(int, int, String)
+     * @see #startOp(int, int, String, boolean, String)
      * @see #finishOp(int, int, String)
      */
     // TODO: Uncomment below annotation once b/73559440 is fixed
@@ -4871,7 +4948,7 @@
      *
      * @see #isOperationActive
      * @see #startWatchingActive
-     * @see #startOp(int, int, String)
+     * @see #startOp(int, int, String, boolean, String)
      * @see #finishOp(int, int, String)
      */
     public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
@@ -4902,7 +4979,7 @@
      *
      * @see #startWatchingActive(int[], OnOpActiveChangedListener)
      * @see #stopWatchingNoted(OnOpNotedListener)
-     * @see #noteOp(String, int, String)
+     * @see #noteOp(String, int, String, String)
      *
      * @hide
      */
@@ -4934,7 +5011,7 @@
      * Unregistering a non-registered callback has no effect.
      *
      * @see #startWatchingNoted(int[], OnOpNotedListener)
-     * @see #noteOp(String, int, String)
+     * @see #noteOp(String, int, String, String)
      *
      * @hide
      */
@@ -4969,15 +5046,16 @@
 
     /**
      * Do a quick check for whether an application might be able to perform an operation.
-     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)}
-     * or {@link #startOp(String, int, String)} for your actual security checks, which also
-     * ensure that the given uid and package name are consistent.  This function can just be
-     * used for a quick check to see if an operation has been disabled for the application,
+     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+     * String)} or {@link #startOp(String, int, String, String)} for your actual security checks,
+     * which also ensure that the given uid and package name are consistent. This function can just
+     * be used for a quick check to see if an operation has been disabled for the application,
      * as an early reject of some work.  This does not modify the time stamp or other data
      * about the operation.
      *
      * <p>Important things this will not do (which you need to ultimate use
-     * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+     * {@link #noteOp(String, int, String, String)} or
+     * {@link #startOp(String, int, String, String)} to cover):</p>
      * <ul>
      *     <li>Verifying the uid and package are consistent, so callers can't spoof
      *     their identity.</li>
@@ -5048,126 +5126,305 @@
     }
 
     /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOp(op, uid, packageName, null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    @Deprecated
+    public int noteOp(int op) {
+        return noteOp(op, Process.myUid(), mContext.getOpPackageName(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    public int noteOp(int op, int uid, @Nullable String packageName) {
+        return noteOp(op, uid, packageName, null);
+    }
+
+    /**
      * Make note of an application performing an operation.  Note that you must pass
      * in both the uid and name of the application to be checked; this function will verify
      * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
+     *
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
-        return noteOp(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     */
-    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
-        return noteOpNoThrow(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Make note of an application performing an operation on behalf of another
-     * application when handling an IPC. Note that you must pass the package name
-     * of the application that is being proxied while its UID will be inferred from
-     * the IPC state; this function will verify that the calling uid and proxied
-     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
-     * succeeds, the last execution time of the operation for the proxied app and
-     * your app will be updated to the current time.
-     * @param op The operation to note.  One of the OPSTR_* constants.
-     * @param proxiedPackageName The name of the application calling into the proxy application.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
-        return noteProxyOp(strOpToOp(op), proxiedPackageName);
-    }
-
-    /**
-     * Like {@link #noteProxyOp(String, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     * @param message A message describing the reason the op was noted
      *
-     * <p>This API requires the package with the {@code proxiedPackageName} to belongs to
-     * {@link Binder#getCallingUid()}.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op.
      */
-    public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
-        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName);
+    public int noteOp(@NonNull String op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        return noteOp(strOpToOp(op), uid, packageName, message);
     }
 
     /**
-     * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid.
+     * Make note of an application performing an operation.  Note that you must pass
+     * in both the uid and name of the application to be checked; this function will verify
+     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time.
+     *
+     * @param op The operation to note.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op.
+     *
+     * @hide
+     */
+    public int noteOp(int op, int uid, @Nullable String packageName, @Nullable String message) {
+        final int mode = noteOpNoThrow(op, uid, packageName, message);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+        }
+        return mode;
+    }
+
+    /**
+     * @deprecated Use {@link #noteOpNoThrow(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOpNoThrow(op, uid, packageName, null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOpNoThrow(int, int, String, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOpNoThrow(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    public int noteOpNoThrow(int op, int uid, String packageName) {
+        return noteOpNoThrow(op, uid, packageName, null);
+    }
+
+    /**
+     * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to note.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     */
+    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+            @Nullable String message) {
+        return noteOpNoThrow(strOpToOp(op), uid, packageName, message);
+    }
+
+    /**
+     * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to note.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @hide
+     */
+    public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        try {
+            int mode = mService.noteOperation(op, uid, packageName);
+            if (mode == MODE_ALLOWED) {
+                markAppOpNoted(uid, packageName, op, message);
+            }
+
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteProxyOp(java.lang.String, java.lang.String, int, java.lang.String)} instead")
+    public int noteProxyOp(int op, @Nullable String proxiedPackageName) {
+        return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another application when
+     * handling an IPC. This function will verify that the calling uid and proxied package name
+     * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+     * time of the operation for the proxied app and your app will be updated to the current time.
+     *
+     * @param op The operation to note. One of the OP_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @param proxiedUid The uid of the proxied application
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+     * if it is not allowed and should be silently ignored (without causing the app to crash).
+     *
+     * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+     * op.
+     *
+     * @hide
+     */
+    public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, message);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+                    + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
+                    + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]);
+        }
+        return mode;
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another application when
+     * handling an IPC. This function will verify that the calling uid and proxied package name
+     * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+     * time of the operation for the proxied app and your app will be updated to the current time.
+     *
+     * @param op The operation to note. One of the OPSTR_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @param proxiedUid The uid of the proxied application
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+     * if it is not allowed and should be silently ignored (without causing the app to crash).
+     *
+     * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+     * op.
+     */
+    public int noteProxyOp(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        return noteProxyOp(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+            int proxiedUid) {
+        return noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, null);
+    }
+
+    /**
+     * Like {@link #noteProxyOp(String, String, int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
      *
      * <p>This API requires package with the {@code proxiedPackageName} to belong to
      * {@code proxiedUid}.
      *
      * @param op The op to note
+     * @param proxiedPackageName The package to note the op for
+     * @param proxiedUid The uid the package belongs to
+     * @param message A message describing the reason the op was noted
+     */
+    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+            int proxiedUid, @Nullable String message) {
+        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+    }
+
+    /**
+     * Like {@link #noteProxyOp(int, String, int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The op to note
      * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
      *                           noted for the "android" package
      * @param proxiedUid The uid the package belongs to
+     * @param message A message describing the reason the op was noted
+     *
+     * @hide
      */
-    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
-            int proxiedUid) {
-        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid);
-    }
+    public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        int myUid = Process.myUid();
 
-    /**
-     * Report that an application has started executing a long-running operation.  Note that you
-     * must pass in both the uid and name of the application to be checked; this function will
-     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time and the operation will be marked as "running".  In this case you must
-     * later call {@link #finishOp(String, int, String)} to report when the application is no
-     * longer performing the operation.
-     * @param op The operation to start.  One of the OPSTR_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
-        return startOp(strOpToOp(op), uid, packageName);
-    }
+        try {
+            int mode = mService.noteProxyOperation(op, myUid, mContext.getOpPackageName(),
+                    proxiedUid, proxiedPackageName);
+            if (mode == MODE_ALLOWED
+                    // Only collect app-ops when the proxy is trusted
+                    && mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, myUid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                markAppOpNoted(proxiedUid, proxiedPackageName, op, message);
+            }
 
-    /**
-     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     */
-    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
-        return startOpNoThrow(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Report that an application is no longer performing an operation that had previously
-     * been started with {@link #startOp(String, int, String)}.  There is no validation of input
-     * or result; the parameters supplied here must be the exact same ones previously passed
-     * in when starting the operation.
-     */
-    public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
-        finishOp(strOpToOp(op), uid, packageName);
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * Do a quick check for whether an application might be able to perform an operation.
-     * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
-     * or {@link #startOp(int, int, String)} for your actual security checks, which also
-     * ensure that the given uid and package name are consistent.  This function can just be
-     * used for a quick check to see if an operation has been disabled for the application,
-     * as an early reject of some work.  This does not modify the time stamp or other data
-     * about the operation.
+     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+     * String)} or {@link #startOp(int, int, String, boolean, String)} for your actual security
+     * checks, which also ensure that the given uid and package name are consistent. This function
+     * can just be used for a quick check to see if an operation has been disabled for the
+     * application, as an early reject of some work.  This does not modify the time stamp or other
+     * data about the operation.
      *
      * <p>Important things this will not do (which you need to ultimate use
-     * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+     * {@link #noteOp(String, int, String, String)} or
+     * {@link #startOp(int, int, String, boolean, String)} to cover):</p>
      * <ul>
      *     <li>Verifying the uid and package are consistent, so callers can't spoof
      *     their identity.</li>
@@ -5259,107 +5516,6 @@
         }
     }
 
-    /**
-     * Make note of an application performing an operation.  Note that you must pass
-     * in both the uid and name of the application to be checked; this function will verify
-     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time.
-     * @param op The operation to note.  One of the OP_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteOp(int op, int uid, String packageName) {
-        final int mode = noteOpNoThrow(op, uid, packageName);
-        if (mode == MODE_ERRORED) {
-            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
-        }
-        return mode;
-    }
-
-    /**
-     * Make note of an application performing an operation on behalf of another
-     * application when handling an IPC. Note that you must pass the package name
-     * of the application that is being proxied while its UID will be inferred from
-     * the IPC state; this function will verify that the calling uid and proxied
-     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
-     * succeeds, the last execution time of the operation for the proxied app and
-     * your app will be updated to the current time.
-     * @param op The operation to note. One of the OPSTR_* constants.
-     * @param proxiedPackageName The name of the application calling into the proxy application.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the proxy or proxied app has been configured to
-     * crash on this op.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteProxyOp(int op, String proxiedPackageName) {
-        int mode = noteProxyOpNoThrow(op, proxiedPackageName);
-        if (mode == MODE_ERRORED) {
-            throw new SecurityException("Proxy package " + mContext.getOpPackageName()
-                    + " from uid " + Process.myUid() + " or calling package "
-                    + proxiedPackageName + " from uid " + Binder.getCallingUid()
-                    + " not allowed to perform " + sOpNames[op]);
-        }
-        return mode;
-    }
-
-    /**
-     * Like {@link #noteProxyOp(int, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
-     * @hide
-     */
-    public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) {
-        try {
-            return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(),
-                    proxiedUid, proxiedPackageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Like {@link #noteProxyOp(int, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
-     *
-     * <p>This API requires the package with {@code proxiedPackageName} to belongs to
-     * {@link Binder#getCallingUid()}.
-     *
-     * @hide
-     */
-    public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
-        return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid());
-    }
-
-    /**
-     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteOpNoThrow(int op, int uid, String packageName) {
-        try {
-            return mService.noteOperation(op, uid, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public int noteOp(int op) {
-        return noteOp(op, Process.myUid(), mContext.getOpPackageName());
-    }
-
     /** @hide */
     @UnsupportedAppUsage
     public static IBinder getToken(IAppOpsService service) {
@@ -5376,54 +5532,86 @@
         }
     }
 
-    /** @hide */
-    public int startOp(int op) {
-        return startOp(op, Process.myUid(), mContext.getOpPackageName());
+
+    /**
+     * @deprecated use {@link #startOp(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOp(op, uid, packageName, null);
     }
 
     /**
-     * Report that an application has started executing a long-running operation.  Note that you
-     * must pass in both the uid and name of the application to be checked; this function will
-     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time and the operation will be marked as "running".  In this case you must
-     * later call {@link #finishOp(int, int, String)} to report when the application is no
-     * longer performing the operation.
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
      *
-     * @param op The operation to start.  One of the OP_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
      * @hide
      */
-    public int startOp(int op, int uid, String packageName) {
-        return startOp(op, uid, packageName, false);
+    @Deprecated
+    public int startOp(int op) {
+        return startOp(op, Process.myUid(), mContext.getOpPackageName(), false, null);
     }
 
     /**
-     * Report that an application has started executing a long-running operation. Similar
-     * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
-     * the operation should succeed since the caller has performed its standard permission
-     * checks which passed and would perform the protected operation for this mode.
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOp(int op, int uid, String packageName) {
+        return startOp(op, uid, packageName, false, null);
+    }
+
+    /**
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+        return startOp(op, uid, packageName, startIfModeDefault, null);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.
+     *
+     * @param op The operation to start.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message Description why op was started
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op or
+     * the package is not in the passed in UID.
+     */
+    public int startOp(@NonNull String op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        return startOp(strOpToOp(op), uid, packageName, false, message);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.
      *
      * @param op The operation to start.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
+     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     * @param message Description why op was started
+     *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
      * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
      * causing the app to crash).
-     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
      *
      * @throws SecurityException If the app has been configured to crash on this op or
      * the package is not in the passed in UID.
      *
      * @hide
      */
-    public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
-        final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+    public int startOp(int op, int uid, @Nullable String packageName, boolean startIfModeDefault,
+            @Nullable String message) {
+        final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault, message);
         if (mode == MODE_ERRORED) {
             throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
         }
@@ -5431,45 +5619,111 @@
     }
 
     /**
-     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     * @hide
+     * @deprecated use {@link #startOpNoThrow(String, int, String, String)} instead
      */
-    public int startOpNoThrow(int op, int uid, String packageName) {
-        return startOpNoThrow(op, uid, packageName, false);
+    @Deprecated
+    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOpNoThrow(op, uid, packageName, null);
     }
 
     /**
-     * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+     * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOpNoThrow(int op, int uid, String packageName) {
+        return startOpNoThrow(op, uid, packageName, false, null);
+    }
+
+    /**
+     * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+        return startOpNoThrow(op, uid, packageName, startIfModeDefault, null);
+    }
+
+    /**
+     * Like {@link #startOp(String, int, String, String)} but instead of throwing a
      * {@link SecurityException} it returns {@link #MODE_ERRORED}.
      *
      * @param op The operation to start.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
+     * @param message Description why op was started
+     *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
      * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
      * causing the app to crash).
+     */
+    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+            @Nullable String message) {
+        return startOpNoThrow(strOpToOp(op), uid, packageName, false, message);
+    }
+
+    /**
+     * Like {@link #startOp(int, int, String, boolean, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to start.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
      * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     * @param message Description why op was started
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
      *
      * @hide
      */
-    public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+    public int startOpNoThrow(int op, int uid, @NonNull String packageName,
+            boolean startIfModeDefault, @Nullable String message) {
         try {
-            return mService.startOperation(getToken(mService), op, uid, packageName,
+            int mode = mService.startOperation(getToken(mService), op, uid, packageName,
                     startIfModeDefault);
+            if (mode == MODE_ALLOWED) {
+                markAppOpNoted(uid, packageName, op, message);
+            }
+
+            return mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Report that an application is no longer performing an operation that had previously
-     * been started with {@link #startOp(int, int, String)}.  There is no validation of input
-     * or result; the parameters supplied here must be the exact same ones previously passed
-     * in when starting the operation.
+     * @deprecated Use {@link #finishOp(String, int, String)} instead
+     *
      * @hide
      */
-    public void finishOp(int op, int uid, String packageName) {
+    @Deprecated
+    public void finishOp(int op) {
+        finishOp(op, Process.myUid(), mContext.getOpPackageName());
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(String, int, String, String)}.  There is no validation of
+     * input or result; the parameters supplied here must be the exact same ones previously passed
+     * in when starting the operation.
+     */
+    public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
+        finishOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(int, int, String, boolean, String)}. There is no
+     * validation of input or result; the parameters supplied here must be the exact same ones
+     * previously passed in when starting the operation.
+     *
+     * @hide
+     */
+    public void finishOp(int op, int uid, @NonNull String packageName) {
         try {
             mService.finishOperation(getToken(mService), op, uid, packageName);
         } catch (RemoteException e) {
@@ -5477,25 +5731,402 @@
         }
     }
 
-    /** @hide */
-    public void finishOp(int op) {
-        finishOp(op, Process.myUid(), mContext.getOpPackageName());
-    }
-
     /**
      * Checks whether the given op for a package is active.
      * <p>
      * If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
      * permission you can query only for your UID.
      *
-     * @see #finishOp(int)
-     * @see #startOp(int)
+     * @see #finishOp(String, int, String)
+     * @see #startOp(String, int, String, String)
      */
     public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) {
         return isOperationActive(strOpToOp(op), uid, packageName);
     }
 
     /**
+     * Start collection of noted appops on this thread.
+     *
+     * <p>Called at the beginning of a two way binder transaction.
+     *
+     * @see #finishNotedAppOpsCollection()
+     *
+     * @hide
+     */
+    public static void startNotedAppOpsCollection(int callingUid) {
+        sBinderThreadCallingUid.set(callingUid);
+    }
+
+    /**
+     * State of a temporarily paused noted app-ops collection.
+     *
+     * @see #pauseNotedAppOpsCollection()
+     *
+     * @hide
+     */
+    public static class PausedNotedAppOpsCollection {
+        final int mUid;
+        final @Nullable long[] mCollectedNotedAppOps;
+
+        PausedNotedAppOpsCollection(int uid, @Nullable long[] collectedNotedAppOps) {
+            mUid = uid;
+            mCollectedNotedAppOps = collectedNotedAppOps;
+        }
+    }
+
+    /**
+     * Temporarily suspend collection of noted app-ops when binder-thread calls into the other
+     * process. During such a call there might be call-backs coming back on the same thread which
+     * should not be accounted to the current collection.
+     *
+     * @return a state needed to resume the collection
+     *
+     * @hide
+     */
+    public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
+        Integer previousUid = sBinderThreadCallingUid.get();
+        if (previousUid != null) {
+            long[] previousCollectedNotedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+
+            sBinderThreadCallingUid.remove();
+            sAppOpsNotedInThisBinderTransaction.remove();
+
+            return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
+        }
+
+        return null;
+    }
+
+    /**
+     * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
+     *
+     * @param prevCollection The state of the previous collection
+     *
+     * @hide
+     */
+    public static void resumeNotedAppOpsCollection(
+            @Nullable PausedNotedAppOpsCollection prevCollection) {
+        if (prevCollection != null) {
+            sBinderThreadCallingUid.set(prevCollection.mUid);
+
+            if (prevCollection.mCollectedNotedAppOps != null) {
+                sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
+            }
+        }
+    }
+
+    /**
+     * Finish collection of noted appops on this thread.
+     *
+     * <p>Called at the end of a two way binder transaction.
+     *
+     * @see #startNotedAppOpsCollection(int)
+     *
+     * @hide
+     */
+    public static void finishNotedAppOpsCollection() {
+        sBinderThreadCallingUid.remove();
+        sAppOpsNotedInThisBinderTransaction.remove();
+    }
+
+    /**
+     * Mark an app-op as noted
+     */
+    private void markAppOpNoted(int uid, @NonNull String packageName, int code,
+            @Nullable String message) {
+        // check it the appops needs to be collected and cache result
+        if (sAppOpsToNote[code] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) {
+            boolean shouldCollectNotes;
+            try {
+                shouldCollectNotes = mService.shouldCollectNotes(code);
+            } catch (RemoteException e) {
+                return;
+            }
+
+            if (shouldCollectNotes) {
+                sAppOpsToNote[code] = SHOULD_COLLECT_NOTE_OP;
+            } else {
+                sAppOpsToNote[code] = SHOULD_NOT_COLLECT_NOTE_OP;
+            }
+        }
+
+        if (sAppOpsToNote[code] != SHOULD_COLLECT_NOTE_OP) {
+            return;
+        }
+
+        Integer binderUid = sBinderThreadCallingUid.get();
+
+        synchronized (sLock) {
+            if (sNotedAppOpsCollector != null && uid == Process.myUid() && packageName.equals(
+                    ActivityThread.currentOpPackageName())) {
+                // This app is noting an app-op for itself. Deliver immediately.
+                sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(code));
+
+                return;
+            }
+        }
+
+        if (binderUid != null && binderUid == uid) {
+            // If this is inside of a two-way binder call: Delivered to caller via
+            // {@link #prefixParcelWithAppOpsIfNeeded}
+            long[] appOpsNotedInThisBinderTransaction;
+
+            appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
+            if (appOpsNotedInThisBinderTransaction == null) {
+                appOpsNotedInThisBinderTransaction = new long[2];
+                sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
+            }
+
+            if (code < 64) {
+                appOpsNotedInThisBinderTransaction[0] |= 1L << code;
+            } else {
+                appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
+            }
+        } else {
+            // Cannot deliver the note synchronous: Hence send it to the system server to
+            // notify the noted process.
+            if (message == null) {
+                // Default message is a stack trace
+                message = getFormattedStackTrace();
+            }
+
+            long token = Binder.clearCallingIdentity();
+            try {
+                mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code, message);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /**
+     * Append app-ops noted in the current two-way binder transaction to parcel.
+     *
+     * <p>This is called on the callee side of a two way binder transaction just before the
+     * transaction returns.
+     *
+     * @param p the parcel to append the noted app-ops to
+     *
+     * @hide
+     */
+    public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
+        long[] notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+        if (notedAppOps == null || (notedAppOps[0] == 0 && notedAppOps[1] == 0)) {
+            return;
+        }
+
+        p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+        p.writeLong(notedAppOps[0]);
+        p.writeLong(notedAppOps[1]);
+    }
+
+    /**
+     * Read app-ops noted during a two-way binder transaction from parcel.
+     *
+     * <p>This is called on the calling side of a two way binder transaction just after the
+     * transaction returns.
+     *
+     * <p>Note: Make sure to keep frameworks/native/libs/binder/Status.cpp::readAndLogNotedAppops
+     * in sync.
+     *
+     * @param p The parcel to read from
+     *
+     * @hide
+     */
+    public static void readAndLogNotedAppops(@NonNull Parcel p) {
+        long[] rawNotedAppOps = new long[2];
+        rawNotedAppOps[0] = p.readLong();
+        rawNotedAppOps[1] = p.readLong();
+
+        if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
+            BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+
+            synchronized (sLock) {
+                for (int code = notedAppOps.nextSetBit(0); code != -1;
+                        code = notedAppOps.nextSetBit(code + 1)) {
+                    if (sNotedAppOpsCollector != null) {
+                        sNotedAppOpsCollector.onNoted(new SyncNotedAppOp(code));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Register a new {@link AppOpsCollector}.
+     *
+     * <p>There can only ever be one collector per process. If there currently is a collector
+     * registered, it will be unregistered.
+     *
+     * <p><b>Only appops related to dangerous permissions are collected.</b>
+     *
+     * @param collector The collector to set or {@code null} to unregister.
+     */
+    public void setNotedAppOpsCollector(@Nullable AppOpsCollector collector) {
+        synchronized (sLock) {
+            if (sNotedAppOpsCollector != null) {
+                try {
+                    mService.stopWatchingAsyncNoted(mContext.getPackageName(),
+                            sNotedAppOpsCollector.mAsyncCb);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+
+            sNotedAppOpsCollector = collector;
+
+            if (sNotedAppOpsCollector != null) {
+                List<AsyncNotedAppOp> missedAsyncOps = null;
+                try {
+                    mService.startWatchingAsyncNoted(mContext.getPackageName(),
+                            sNotedAppOpsCollector.mAsyncCb);
+                    missedAsyncOps = mService.extractAsyncOps(mContext.getPackageName());
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+
+                if (missedAsyncOps != null) {
+                    int numMissedAsyncOps = missedAsyncOps.size();
+                    for (int i = 0; i < numMissedAsyncOps; i++) {
+                        final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i);
+                        if (sNotedAppOpsCollector != null) {
+                            sNotedAppOpsCollector.getAsyncNotedExecutor().execute(
+                                    () -> sNotedAppOpsCollector.onAsyncNoted(
+                                            asyncNotedAppOp));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} iff the process currently is currently collecting noted appops.
+     *
+     * @see #setNotedAppOpsCollector(AppOpsCollector)
+     *
+     * @hide
+     */
+    public static boolean isCollectingNotedAppOps() {
+        synchronized (sLock) {
+            return sNotedAppOpsCollector != null;
+        }
+    }
+
+    /**
+     * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
+     * appops.
+     *
+     * <p><b>Only appops related to dangerous permissions are collected.</b>
+     */
+    public abstract static class AppOpsCollector {
+        /** Callback registered with the system. This will receive the async notes ops */
+        private final IAppOpsAsyncNotedCallback mAsyncCb = new IAppOpsAsyncNotedCallback.Stub() {
+            @Override
+            public void opNoted(AsyncNotedAppOp op) {
+                Preconditions.checkNotNull(op);
+
+                getAsyncNotedExecutor().execute(() -> onAsyncNoted(op));
+            }
+        };
+
+        /**
+         * @return The executor for the system to use when calling {@link #onAsyncNoted}.
+         */
+        public @NonNull Executor getAsyncNotedExecutor() {
+            return new HandlerExecutor(Handler.getMain());
+        }
+
+        /**
+         * Called when an app-op was noted for this package inside of a two-way binder-call.
+         *
+         * <p>Called on the calling thread just after executing the binder-call. This allows
+         * the app to e.g. collect stack traces to figure out where the access came from.
+         *
+         * @param op The op noted
+         */
+        public abstract void onNoted(@NonNull SyncNotedAppOp op);
+
+        /**
+         * Called when this app noted an app-op for its own package.
+         *
+         * <p>Called on the thread the noted the op. This allows the app to e.g. collect stack
+         * traces to figure out where the access came from.
+         *
+         * @param op The op noted
+         */
+        public abstract void onSelfNoted(@NonNull SyncNotedAppOp op);
+
+        /**
+         * Called when an app-op was noted for this package which cannot be delivered via the other
+         * two mechanisms.
+         *
+         * <p>Called as soon as possible after the app-op was noted, but the delivery delay is not
+         * guaranteed. Due to how async calls work in Android this might even be delivered slightly
+         * before the private data is delivered to the app.
+         *
+         * <p>If the app is not running or no {@link AppOpsCollector} is registered a small amount
+         * of noted app-ops are buffered and then delivered as soon as a collector is registered.
+         *
+         * @param asyncOp The op noted
+         */
+        public abstract void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp);
+    }
+
+    /**
+     * Generate a stack trace used for noted app-ops logging.
+     *
+     * <p>This strips away the first few and last few stack trace elements as they are not
+     * interesting to apps.
+     */
+    private static String getFormattedStackTrace() {
+        StackTraceElement[] trace = new Exception().getStackTrace();
+
+        int firstInteresting = 0;
+        for (int i = 0; i < trace.length; i++) {
+            if (trace[i].getClassName().startsWith(AppOpsManager.class.getName())
+                    || trace[i].getClassName().startsWith(Parcel.class.getName())
+                    || trace[i].getClassName().contains("$Stub$Proxy")
+                    || trace[i].getClassName().startsWith(DatabaseUtils.class.getName())
+                    || trace[i].getClassName().startsWith("android.content.ContentProviderProxy")
+                    || trace[i].getClassName().startsWith(ContentResolver.class.getName())) {
+                firstInteresting = i;
+            } else {
+                break;
+            }
+        }
+
+        int lastInteresting = trace.length - 1;
+        for (int i = trace.length - 1; i >= 0; i--) {
+            if (trace[i].getClassName().startsWith(HandlerThread.class.getName())
+                    || trace[i].getClassName().startsWith(Handler.class.getName())
+                    || trace[i].getClassName().startsWith(Looper.class.getName())
+                    || trace[i].getClassName().startsWith(Binder.class.getName())
+                    || trace[i].getClassName().startsWith(RuntimeInit.class.getName())
+                    || trace[i].getClassName().startsWith(ZygoteInit.class.getName())
+                    || trace[i].getClassName().startsWith(ActivityThread.class.getName())
+                    || trace[i].getClassName().startsWith(Method.class.getName())
+                    || trace[i].getClassName().startsWith("com.android.server.SystemServer")) {
+                lastInteresting = i;
+            } else {
+                break;
+            }
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = firstInteresting; i <= lastInteresting; i++) {
+            sb.append(trace[i]);
+            if (i != lastInteresting) {
+                sb.append('\n');
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * Checks whether the given op for a UID and package is active.
      *
      * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
@@ -5504,7 +6135,7 @@
      * @see #startWatchingActive(int[], OnOpActiveChangedListener)
      * @see #stopWatchingMode(OnOpChangedListener)
      * @see #finishOp(int)
-     * @see #startOp(int)
+     * @see #startOp(int, int, String, boolean, String)
      *
      * @hide */
     @TestApi
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0478ac8..d74399c 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -127,6 +127,7 @@
      * @hide
      */
     public static final boolean DEBUG_TRACE_GRANTS = false;
+    public static final boolean DEBUG_TRACE_PERMISSION_UPDATES = false;
 
     private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB
 
@@ -690,7 +691,7 @@
             UserHandle user) {
         if (DEBUG_TRACE_GRANTS
                 && shouldTraceGrant(packageName, permissionName, user.getIdentifier())) {
-            Log.i(TAG, "App " + mContext.getPackageName() + " is granting "
+            Log.i(TAG, "App " + mContext.getPackageName() + " is granting " + packageName + " "
                     + permissionName + " for user " + user.getIdentifier(), new RuntimeException());
         }
         try {
@@ -708,9 +709,9 @@
 
     @Override
     public void revokeRuntimePermission(String packageName, String permName, UserHandle user) {
-        if (DEBUG_TRACE_GRANTS
+        if (DEBUG_TRACE_PERMISSION_UPDATES
                 && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
-            Log.i(TAG, "App " + mContext.getPackageName() + " is revoking "
+            Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " "
                     + permName + " for user " + user.getIdentifier(), new RuntimeException());
         }
         try {
@@ -734,9 +735,10 @@
     @Override
     public void updatePermissionFlags(String permName, String packageName,
             int flagMask, int flagValues, UserHandle user) {
-        if (DEBUG_TRACE_GRANTS
+        if (DEBUG_TRACE_PERMISSION_UPDATES
                 && shouldTraceGrant(packageName, permName, user.getIdentifier())) {
             Log.i(TAG, "App " + mContext.getPackageName() + " is updating flags for "
+                    + packageName + " "
                     + permName + " for user " + user.getIdentifier() + ": "
                     + DebugUtils.flagsToString(PackageManager.class, "FLAG_PERMISSION_", flagMask)
                     + " := " + DebugUtils.flagsToString(
diff --git a/core/java/android/app/AsyncNotedAppOp.aidl b/core/java/android/app/AsyncNotedAppOp.aidl
new file mode 100644
index 0000000..ebfefd0
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app;
+
+parcelable AsyncNotedAppOp;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
new file mode 100644
index 0000000..df6533a
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -0,0 +1,245 @@
+/*
+ * 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 android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * When an {@link AppOpsManager#noteOp(String, int, String, String) app-op is noted} and the
+ * app the app-op is noted for has a {@link AppOpsManager.AppOpsCollector} registered the note-event
+ * needs to be delivered to the collector. Usually this is done via an {@link SyncNotedAppOp}, but
+ * in some cases this is not possible. In this case an {@link AsyncNotedAppOp} is send to the system
+ * server and then forwarded to the {@link AppOpsManager.AppOpsCollector} in the app.
+ */
+@Immutable
+@DataClass(genEqualsHashCode = true,
+        genAidl = true,
+        genHiddenConstructor = true)
+// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
+//   getter
+@DataClass.Suppress({"getOpCode"})
+public final class AsyncNotedAppOp implements Parcelable {
+    /** Op that was noted */
+    private final @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+
+    /** Uid that noted the op */
+    private final @IntRange(from = 0) int mNotingUid;
+
+    /**
+     * Package that noted the op. {@code null} if the package name that noted the op could be not
+     * be determined (e.g. when the op is noted from native code).
+     */
+    private final @Nullable String mNotingPackageName;
+
+    /** Message associated with the noteOp. This message is set by the app noting the op */
+    private final @NonNull String mMessage;
+
+    /** Milliseconds since epoch when the op was noted */
+    private final @IntRange(from = 0) long mTime;
+
+    /**
+     * @return Op that was noted.
+     */
+    public @NonNull String getOp() {
+        return AppOpsManager.opToPublicName(mOpCode);
+    }
+
+
+
+    // Code below generated by codegen v1.0.0.
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    /**
+     * Creates a new AsyncNotedAppOp.
+     *
+     * @param opCode
+     *   Op that was noted
+     * @param notingUid
+     *   Uid that noted the op
+     * @param notingPackageName
+     *   Package that noted the op
+     * @param message
+     *   Message associated with the noteOp. This message is set by the app noting the op
+     * @param time
+     *   Milliseconds since epoch when the op was noted
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public AsyncNotedAppOp(
+            @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
+            @IntRange(from = 0) int notingUid,
+            @Nullable String notingPackageName,
+            @NonNull String message,
+            @IntRange(from = 0) long time) {
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mNotingUid = notingUid;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mNotingUid,
+                "from", 0);
+        this.mNotingPackageName = notingPackageName;
+        this.mMessage = message;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessage);
+        this.mTime = time;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mTime,
+                "from", 0);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Uid that noted the op
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) int getNotingUid() {
+        return mNotingUid;
+    }
+
+    /**
+     * Package that noted the op
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getNotingPackageName() {
+        return mNotingPackageName;
+    }
+
+    /**
+     * Message associated with the noteOp. This message is set by the app noting the op
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Milliseconds since epoch when the op was noted
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) long getTime() {
+        return mTime;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        AsyncNotedAppOp that = (AsyncNotedAppOp) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mOpCode == that.mOpCode
+                && mNotingUid == that.mNotingUid
+                && java.util.Objects.equals(mNotingPackageName, that.mNotingPackageName)
+                && java.util.Objects.equals(mMessage, that.mMessage)
+                && mTime == that.mTime;
+    }
+
+    @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 + mOpCode;
+        _hash = 31 * _hash + mNotingUid;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mNotingPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mMessage);
+        _hash = 31 * _hash + Long.hashCode(mTime);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(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 (mNotingPackageName != null) flg |= 0x4;
+        dest.writeByte(flg);
+        dest.writeInt(mOpCode);
+        dest.writeInt(mNotingUid);
+        if (mNotingPackageName != null) dest.writeString(mNotingPackageName);
+        dest.writeString(mMessage);
+        dest.writeLong(mTime);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
+            = new Parcelable.Creator<AsyncNotedAppOp>() {
+        @Override
+        public AsyncNotedAppOp[] newArray(int size) {
+            return new AsyncNotedAppOp[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            int opCode = in.readInt();
+            int notingUid = in.readInt();
+            String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+            String message = in.readString();
+            long time = in.readLong();
+            return new AsyncNotedAppOp(
+                    opCode,
+                    notingUid,
+                    notingPackageName,
+                    message,
+                    time);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1566503083973L,
+            codegenVersion = "1.0.0",
+            sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index bce243c..905f475 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
 import android.app.SharedElementCallback.OnSharedElementsReadyListener;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -66,13 +67,16 @@
     private final boolean mIsCrossTask;
     private Drawable mReplacedBackground;
     private ArrayList<String> mPendingExitNames;
+    private Runnable mOnTransitionComplete;
 
-    public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
-            ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
+    EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
+            ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask,
+            @NonNull Runnable onTransitionComplete) {
         super(activity.getWindow(), sharedElementNames,
                 getListener(activity, isReturning && !isCrossTask), isReturning);
         mActivity = activity;
         mIsCrossTask = isCrossTask;
+        mOnTransitionComplete = onTransitionComplete;
         setResultReceiver(resultReceiver);
         prepareEnter();
         Bundle resultReceiverBundle = new Bundle();
@@ -578,6 +582,10 @@
                 window.setBackgroundDrawable(null);
             }
         }
+        if (mOnTransitionComplete != null) {
+            mOnTransitionComplete.run();
+            mOnTransitionComplete = null;
+        }
     }
 
     private void sharedElementTransitionStarted() {
@@ -672,6 +680,10 @@
             mBackgroundAnimator.cancel();
             mBackgroundAnimator = null;
         }
+        if (mOnTransitionComplete != null) {
+            mOnTransitionComplete.run();
+            mOnTransitionComplete = null;
+        }
         super.clearState();
     }
 
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 48a711e..68824cd 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -48,7 +48,7 @@
  */
 class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
     private static final String TAG = "ExitTransitionCoordinator";
-    private static final long MAX_WAIT_MS = 1000;
+    static long sMaxWaitMillis = 1000;
 
     private Bundle mSharedElementBundle;
     private boolean mExitNotified;
@@ -120,7 +120,7 @@
 
     private void delayCancel() {
         if (mHandler != null) {
-            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
+            mHandler.sendEmptyMessageDelayed(MSG_CANCEL, sMaxWaitMillis);
         }
     }
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 48ca716..dca00a2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -287,7 +287,8 @@
     void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask,
             in StrictMode.ViolationInfo crashInfo);
     boolean isTopActivityImmersive();
-    void crashApplication(int uid, int initialPid, in String packageName, int userId, in String message);
+    void crashApplication(int uid, int initialPid, in String packageName, int userId,
+            in String message, boolean force);
     @UnsupportedAppUsage
     String getProviderMimeType(in Uri uri, int userId);
     // Cause the specified process to dump the specified heap.
@@ -350,11 +351,13 @@
     // Request a heap dump for the system server.
     void requestSystemServerHeapDump();
 
-    // Deprecated - This method is only used by a few internal components and it will soon be
-    // replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+    // Deprecated - This method is only used by a few internal components and it will soon start
+    // using bug report API (which will be restricted to a few, pre-defined apps).
     // No new code should be calling it.
     @UnsupportedAppUsage
     void requestBugReport(int bugreportType);
+    void requestBugReportWithDescription(in @nullable String shareTitle,
+                in @nullable String shareDescription, int bugreportType);
 
     /**
      *  Takes a telephony bug report and notifies the user with the title and description
@@ -369,7 +372,7 @@
     void requestTelephonyBugReport(in String shareTitle, in String shareDescription);
 
     /**
-     *  Deprecated - This method is only used by Wifi, and it will soon be replaced by a proper
+     *  Deprecated - This method is only used by Wifi, and it will soon start using
      *  bug report API.
      *
      *  Takes a minimal bugreport of Wifi-related state.
@@ -381,6 +384,12 @@
      *          parameters cannot be encoding to an UTF-8 charset.
      */
     void requestWifiBugReport(in String shareTitle, in String shareDescription);
+    void requestInteractiveBugReportWithDescription(in String shareTitle,
+            in String shareDescription);
+
+    void requestInteractiveBugReport();
+    void requestFullBugReport();
+    void requestRemoteBugReport();
 
     @UnsupportedAppUsage
     Intent getIntentForIntentSender(in IIntentSender sender);
@@ -397,23 +406,6 @@
     List<ActivityManager.StackInfo> getAllStackInfos();
     @UnsupportedAppUsage
     void moveTaskToStack(int taskId, int stackId, boolean toTop);
-    /**
-     * Resizes the input stack id to the given bounds.
-     *
-     * @param stackId Id of the stack to resize.
-     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     * @param allowResizeInDockedMode True if the resize should be allowed when the docked stack is
-     *                                active.
-     * @param preserveWindows True if the windows of activities contained in the stack should be
-     *                        preserved.
-     * @param animate True if the stack resize should be animated.
-     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
-     *                          default animation duration should be used.
-     * @throws RemoteException
-     */
-    @UnsupportedAppUsage
-    void resizeStack(int stackId, in Rect bounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration);
     void setFocusedStack(int stackId);
     ActivityManager.StackInfo getFocusedStackInfo();
     @UnsupportedAppUsage
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 47b784b..dda3bb5 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -234,21 +234,15 @@
     void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
     void moveTaskToStack(int taskId, int stackId, boolean toTop);
     /**
-     * Resizes the input stack id to the given bounds.
+     * Resizes the input pinned stack to the given bounds with animation.
      *
-     * @param stackId Id of the stack to resize.
+     * @param stackId Id of the pinned stack to resize.
      * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     * @param allowResizeInDockedMode True if the resize should be allowed when the docked stack is
-     *                                active.
-     * @param preserveWindows True if the windows of activities contained in the stack should be
-     *                        preserved.
-     * @param animate True if the stack resize should be animated.
      * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
      *                          default animation duration should be used.
      * @throws RemoteException
      */
-    void resizeStack(int stackId, in Rect bounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration);
+    void animateResizePinnedStack(int stackId, in Rect bounds, int animationDuration);
     boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
             boolean animate, in Rect initialBounds, boolean showRecents);
     /**
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 61867ea..750020e 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -178,6 +178,13 @@
      */
     void onSingleTaskDisplayDrawn(int displayId);
 
+    /*
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    void onSingleTaskDisplayEmpty(int displayId);
+
     /**
      * Called when a task is reparented to a stack on a different display.
      *
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 6677587..9b667a1 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -32,7 +32,6 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -508,13 +507,6 @@
         }
     }
 
-    /** @removed */
-    @Deprecated
-    public void dismissKeyguard(@NonNull Activity activity,
-            @Nullable KeyguardDismissCallback callback, @Nullable Handler handler) {
-        requestDismissKeyguard(activity, callback);
-    }
-
     /**
      * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
      * be dismissed.
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8987b23..f0b3546 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -883,48 +883,6 @@
             }
         }
 
-        // /apex/com.android.art/lib, /vendor/lib, /odm/lib and /product/lib
-        // are added to the native lib search paths of the classloader.
-        // Note that this is done AFTER the classloader is
-        // created by ApplicationLoaders.getDefault().getClassLoader(...). The
-        // reason is because if we have added the paths when creating the classloader
-        // above, the paths are also added to the search path of the linker namespace
-        // 'classloader-namespace', which will allow ALL libs in the paths to apps.
-        // Since only the libs listed in <partition>/etc/public.libraries.txt can be
-        // available to apps, we shouldn't add the paths then.
-        //
-        // However, we need to add the paths to the classloader (Java) though. This
-        // is because when a native lib is requested via System.loadLibrary(), the
-        // classloader first tries to find the requested lib in its own native libs
-        // search paths. If a lib is not found in one of the paths, dlopen() is not
-        // called at all. This can cause a problem that a vendor public native lib
-        // is accessible when directly opened via dlopen(), but inaccesible via
-        // System.loadLibrary(). In order to prevent the problem, we explicitly
-        // add the paths only to the classloader, and not to the native loader
-        // (linker namespace).
-        List<String> extraLibPaths = new ArrayList<>(4);
-        String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
-        if (!defaultSearchPaths.contains("/apex/com.android.art/lib")) {
-            extraLibPaths.add("/apex/com.android.art/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/vendor/lib")) {
-            extraLibPaths.add("/vendor/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/odm/lib")) {
-            extraLibPaths.add("/odm/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/product/lib")) {
-            extraLibPaths.add("/product/lib" + abiSuffix);
-        }
-        if (!extraLibPaths.isEmpty()) {
-            StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
-            try {
-                ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
-            } finally {
-                setThreadPolicy(oldPolicy);
-            }
-        }
-
         if (addedPaths != null && addedPaths.size() > 0) {
             final String add = TextUtils.join(File.pathSeparator, addedPaths);
             ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ac53118..2f03ed4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3274,11 +3274,8 @@
      *
      * @param requiresFreeform requires the remoteinput to allow freeform or not.
      * @return the result pair, {@code null} if no result is found.
-     *
-     * @hide
      */
     @Nullable
-    @SystemApi
     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
         if (actions == null) {
             return null;
@@ -3301,11 +3298,9 @@
     }
 
     /**
-     * Returns the actions that are contextual out of the actions in this notification.
-     *
-     * @hide
+     * Returns the actions that are contextual (that is, suggested because of the content of the
+     * notification) out of the actions in this notification.
      */
-    @SystemApi
     public @NonNull List<Notification.Action> getContextualActions() {
         if (actions == null) return Collections.emptyList();
 
@@ -7705,11 +7700,11 @@
             }
 
             /**
-             * @return A list of messages read from the bundles.
-             *
-             * @hide
+             * Returns a list of messages read from the given bundle list, e.g.
+             * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
              */
-            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+            @NonNull
+            public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
                 if (bundles == null) {
                     return new ArrayList<>();
                 }
@@ -7726,13 +7721,12 @@
             }
 
             /**
-             * @return The message that is stored in the bundle or null if the message couldn't be
-             * resolved.
-             *
+             * Returns the message that is stored in the bundle (e.g. one of the values in the lists
+             * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
+             * message couldn't be resolved.
              * @hide
              */
             @Nullable
-            @SystemApi
             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
                 try {
                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
@@ -8156,7 +8150,9 @@
                 Action action, StandardTemplateParams p) {
             final boolean tombstone = (action.actionIntent == null);
             container.setViewVisibility(buttonId, View.VISIBLE);
-            container.setImageViewIcon(buttonId, action.getIcon());
+            if (buttonId != R.id.media_seamless) {
+                container.setImageViewIcon(buttonId, action.getIcon());
+            }
 
             // If the action buttons should not be tinted, then just use the default
             // notification color. Otherwise, just use the passed-in color.
@@ -8210,6 +8206,10 @@
                     view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
                 }
             }
+            bindMediaActionButton(view, R.id.media_seamless, new Action(
+                    R.drawable.ic_media_seamless, mBuilder.mContext.getString(
+                            com.android.internal.R.string.ext_media_seamless_action), null), p);
+            view.setViewVisibility(R.id.media_seamless, View.GONE);
             handleImage(view);
             // handle the content margin
             int endMargin = R.dimen.notification_content_margin_end;
@@ -8542,24 +8542,34 @@
          * If set and the app creating the bubble is in the foreground, the bubble will be posted
          * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
          *
-         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+        public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
 
         /**
          * If set and the app posting the bubble is in the foreground, the bubble will
          * be posted <b>without</b> the associated notification in the notification shade.
          *
-         * <p>If the app posting the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble, or if the user has seen the content in the notification and the
          * notification is no longer relevant.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
+        public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
                 Icon icon, int height, @DimenRes int heightResId) {
@@ -8675,11 +8685,21 @@
             out.writeInt(mDesiredHeightResId);
         }
 
-        private void setFlags(int flags) {
+        /**
+         * @hide
+         */
+        public void setFlags(int flags) {
             mFlags = flags;
         }
 
         /**
+         * @hide
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
          * Builder to construct a {@link BubbleMetadata} object.
          */
         public static final class Builder {
@@ -8795,6 +8815,8 @@
              * {@link #getIntent()} in a floating window).
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
@@ -8813,6 +8835,8 @@
              * the notification shade.
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 93e4ddc..20d977b 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -710,6 +710,14 @@
     }
 
     /**
+     * Returns whether the user has chosen the sound of this channel.
+     * @see #getSound()
+     */
+    public boolean hasUserSetSound() {
+        return (mUserLockedFields & USER_LOCKED_SOUND) != 0;
+    }
+
+    /**
      * @hide
      */
     public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 9162626..9f865b4 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -16,17 +16,19 @@
 
 package android.app;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.SharedPreferences;
+import android.os.Build;
 import android.os.FileUtils;
 import android.os.Looper;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructStat;
 import android.system.StructTimespec;
-import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -62,6 +64,15 @@
     /** If a fsync takes more than {@value #MAX_FSYNC_DURATION_MILLIS} ms, warn */
     private static final long MAX_FSYNC_DURATION_MILLIS = 256;
 
+    /**
+     * There will now be a callback to {@link
+     * OnSharedPreferenceChangeListener#onSharedPreferenceChanged(SharedPreferences, String)} with
+     * a {@code null} key on {@link Editor#clear()}.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long CALLBACK_ON_CLEAR_CHANGE = 119147584L;
+
     // Lock ordering rules:
     //  - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock
     //  - acquire mWritingToDiskLock before EditorImpl.mLock
@@ -94,10 +105,6 @@
     private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
             new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
 
-    @GuardedBy("mLock")
-    private final WeakHashMap<OnSharedPreferencesClearListener, Object> mClearListeners =
-            new WeakHashMap<>();
-
     /** Current memory state (always increasing) */
     @GuardedBy("this")
     private long mCurrentMemoryStateGeneration;
@@ -258,28 +265,6 @@
         }
     }
 
-    @Override
-    public void registerOnSharedPreferencesClearListener(
-            @NonNull OnSharedPreferencesClearListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null.");
-        }
-        synchronized (mLock) {
-            mClearListeners.put(listener, CONTENT);
-        }
-    }
-
-    @Override
-    public void unregisterOnSharedPreferencesClearListener(
-            @NonNull OnSharedPreferencesClearListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null.");
-        }
-        synchronized (mLock) {
-            mClearListeners.remove(listener);
-        }
-    }
-
     @GuardedBy("mLock")
     private void awaitLoadedLocked() {
         if (!mLoaded) {
@@ -388,10 +373,9 @@
     // Return value from EditorImpl#commitToMemory()
     private static class MemoryCommitResult {
         final long memoryStateGeneration;
+        final boolean keysCleared;
         @Nullable final List<String> keysModified;
-        @Nullable final Set<String> keysCleared;
         @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
-        @Nullable final Set<OnSharedPreferencesClearListener> clearListeners;
         final Map<String, Object> mapToWriteToDisk;
         final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
 
@@ -399,16 +383,14 @@
         volatile boolean writeToDiskResult = false;
         boolean wasWritten = false;
 
-        private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
+        private MemoryCommitResult(long memoryStateGeneration, boolean keysCleared,
+                @Nullable List<String> keysModified,
                 @Nullable Set<OnSharedPreferenceChangeListener> listeners,
-                @Nullable Set<String> keysCleared,
-                @Nullable Set<OnSharedPreferencesClearListener> clearListeners,
                 Map<String, Object> mapToWriteToDisk) {
             this.memoryStateGeneration = memoryStateGeneration;
+            this.keysCleared = keysCleared;
             this.keysModified = keysModified;
             this.listeners = listeners;
-            this.keysCleared = keysCleared;
-            this.clearListeners = clearListeners;
             this.mapToWriteToDisk = mapToWriteToDisk;
         }
 
@@ -526,16 +508,14 @@
             // SharedPreferences instance back, which has the
             // changes reflected in memory.
             notifyListeners(mcr);
-            notifyClearListeners(mcr);
         }
 
         // Returns true if any changes were made
         private MemoryCommitResult commitToMemory() {
             long memoryStateGeneration;
+            boolean keysCleared = false;
             List<String> keysModified = null;
-            Set<String> keysCleared = null;
             Set<OnSharedPreferenceChangeListener> listeners = null;
-            Set<OnSharedPreferencesClearListener> clearListeners = null;
             Map<String, Object> mapToWriteToDisk;
 
             synchronized (SharedPreferencesImpl.this.mLock) {
@@ -557,23 +537,16 @@
                     keysModified = new ArrayList<String>();
                     listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                 }
-                boolean hasClearListeners = !mClearListeners.isEmpty();
-                if (hasClearListeners) {
-                    keysCleared = new ArraySet<>();
-                    clearListeners = new HashSet<>(mClearListeners.keySet());
-                }
 
                 synchronized (mEditorLock) {
                     boolean changesMade = false;
 
                     if (mClear) {
                         if (!mapToWriteToDisk.isEmpty()) {
-                            if (hasClearListeners) {
-                                keysCleared.addAll(mapToWriteToDisk.keySet());
-                            }
                             changesMade = true;
                             mapToWriteToDisk.clear();
                         }
+                        keysCleared = true;
                         mClear = false;
                     }
 
@@ -613,8 +586,8 @@
                     memoryStateGeneration = mCurrentMemoryStateGeneration;
                 }
             }
-            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
-                    keysCleared, clearListeners, mapToWriteToDisk);
+            return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
+                    listeners, mapToWriteToDisk);
         }
 
         @Override
@@ -641,16 +614,21 @@
                 }
             }
             notifyListeners(mcr);
-            notifyClearListeners(mcr);
             return mcr.writeToDiskResult;
         }
 
         private void notifyListeners(final MemoryCommitResult mcr) {
-            if (mcr.listeners == null || mcr.keysModified == null ||
-                mcr.keysModified.size() == 0) {
+            if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
                 return;
             }
             if (Looper.myLooper() == Looper.getMainLooper()) {
+                if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
+                    for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
+                        if (listener != null) {
+                            listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
+                        }
+                    }
+                }
                 for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
                     final String key = mcr.keysModified.get(i);
                     for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
@@ -664,24 +642,6 @@
                 ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
             }
         }
-
-        private void notifyClearListeners(final MemoryCommitResult mcr) {
-            if (mcr.clearListeners == null || mcr.keysCleared == null
-                    || mcr.keysCleared.isEmpty()) {
-                return;
-            }
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                for (OnSharedPreferencesClearListener listener : mcr.clearListeners) {
-                    if (listener != null) {
-                        listener.onSharedPreferencesClear(SharedPreferencesImpl.this,
-                                mcr.keysCleared);
-                    }
-                }
-            } else {
-                // Run this function on the main thread.
-                ActivityThread.sMainThreadHandler.post(() -> notifyClearListeners(mcr));
-            }
-        }
     }
 
     /**
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 205e7a1..28413be 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -620,6 +620,7 @@
             mNotificationIcons = true;
         }
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
new file mode 100644
index 0000000..f7b83d4
--- /dev/null
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Description of an app-op that was noted for the current process.
+ *
+ * <p>This is either delivered after a
+ * {@link AppOpsManager.AppOpsCollector#onNoted(SyncNotedAppOp) two way binder call} or
+ * when the app
+ * {@link AppOpsManager.AppOpsCollector#onSelfNoted(SyncNotedAppOp) notes an app-op for
+ * itself}.
+ */
+@Immutable
+public final class SyncNotedAppOp {
+    private final int mOpCode;
+
+    /**
+     * @return The op that was noted.
+     */
+    public @NonNull String getOp() {
+        return AppOpsManager.opToPublicName(mOpCode);
+    }
+
+    /**
+     * Create a new sync op description
+     *
+     * @param opCode The op that was noted
+     *
+     * @hide
+     */
+    public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode) {
+        mOpCode = opCode;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof SyncNotedAppOp)) {
+            return false;
+        }
+
+        return mOpCode == ((SyncNotedAppOp) other).mOpCode;
+    }
+
+    @Override
+    public int hashCode() {
+        return mOpCode;
+    }
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e4fd566..0499337 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,6 +152,7 @@
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
 import android.os.storage.StorageManager;
+import android.os.telephony.TelephonyRegistryManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
@@ -606,6 +607,13 @@
                 return new TelephonyManager(ctx.getOuterContext());
             }});
 
+        registerService(Context.TELEPHONY_REGISTRY_SERVICE, TelephonyRegistryManager.class,
+            new CachedServiceFetcher<TelephonyRegistryManager>() {
+                @Override
+                public TelephonyRegistryManager createService(ContextImpl ctx) {
+                    return new TelephonyRegistryManager();
+                }});
+
         registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
                 new CachedServiceFetcher<SubscriptionManager>() {
             @Override
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index def1f45..35c7104 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -26,6 +26,15 @@
                     "include-filter": "com.android.server.appop"
                 }
             ]
+        },
+        {
+            "file_patterns": ["(/|^)AppOpsManager.java"],
+            "name": "CtsPermission2TestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                }
+            ]
         }
     ],
     "postsubmit": [
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index e3a0e11..46045fa 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -180,6 +180,10 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
     }
 
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index f9b96c5..fd93450 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService.Callbacks;
 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -1246,7 +1246,7 @@
                 }
 
                 @Override
-                public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+                public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
                     /* do nothing */
                     return false;
                 }
diff --git a/core/java/android/app/Vr2dDisplayProperties.java b/core/java/android/app/Vr2dDisplayProperties.java
index fc200bf..d2a49fb 100644
--- a/core/java/android/app/Vr2dDisplayProperties.java
+++ b/core/java/android/app/Vr2dDisplayProperties.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -74,6 +75,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "Vr2dDisplayProperties{"
@@ -86,7 +88,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ff5a043..64ddfc1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1551,7 +1551,8 @@
      * scopes will be sent in an {@code ArrayList<String>} extra identified by the
      * {@link #EXTRA_DELEGATION_SCOPES} key.
      *
-     * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
+     * <p class="note"><b>Note:</b> This is a protected intent that can only be sent by the
+     * system.</p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED =
@@ -2531,14 +2532,13 @@
     public static final int PASSWORD_QUALITY_ALPHANUMERIC = 0x50000;
 
     /**
-     * Constant for {@link #setPasswordQuality}: the user must have entered a
-     * password containing at least a letter, a numerical digit and a special
-     * symbol, by default. With this password quality, passwords can be
-     * restricted to contain various sets of characters, like at least an
-     * uppercase letter, etc. These are specified using various methods,
-     * like {@link #setPasswordMinimumLowerCase(ComponentName, int)}. Note
-     * that quality constants are ordered so that higher values are more
-     * restrictive.
+     * Constant for {@link #setPasswordQuality}: allows the admin to set precisely how many
+     * characters of various types the password should contain to satisfy the policy. The admin
+     * should set these requirements via {@link #setPasswordMinimumLetters},
+     * {@link #setPasswordMinimumNumeric}, {@link #setPasswordMinimumSymbols},
+     * {@link #setPasswordMinimumUpperCase}, {@link #setPasswordMinimumLowerCase},
+     * {@link #setPasswordMinimumNonLetter}, and {@link #setPasswordMinimumLength}.
+     * Note that quality constants are ordered so that higher values are more restrictive.
      */
     public static final int PASSWORD_QUALITY_COMPLEX = 0x60000;
 
@@ -2610,6 +2610,7 @@
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param quality The new desired quality. One of {@link #PASSWORD_QUALITY_UNSPECIFIED},
+     *            {@link #PASSWORD_QUALITY_BIOMETRIC_WEAK},
      *            {@link #PASSWORD_QUALITY_SOMETHING}, {@link #PASSWORD_QUALITY_NUMERIC},
      *            {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX}, {@link #PASSWORD_QUALITY_ALPHABETIC},
      *            {@link #PASSWORD_QUALITY_ALPHANUMERIC} or {@link #PASSWORD_QUALITY_COMPLEX}.
@@ -3186,10 +3187,7 @@
      * same as any password in the history. Note that the current password will remain until the
      * user has set a new one, so the change does not take place immediately. To prompt the user for
      * a new password, use {@link #ACTION_SET_NEW_PASSWORD} or
-     * {@link #ACTION_SET_NEW_PARENT_PROFILE_PASSWORD} after setting this value. This constraint is
-     * only imposed if the administrator has also requested either {@link #PASSWORD_QUALITY_NUMERIC}
-     * , {@link #PASSWORD_QUALITY_NUMERIC_COMPLEX} {@link #PASSWORD_QUALITY_ALPHABETIC}, or
-     * {@link #PASSWORD_QUALITY_ALPHANUMERIC} with {@link #setPasswordQuality}.
+     * {@link #ACTION_SET_NEW_PARENT_PROFILE_PASSWORD} after setting this value.
      * <p>
      * On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
      * password history length is always 0.
@@ -5764,21 +5762,6 @@
     /**
      * @hide
      */
-    @UnsupportedAppUsage
-    @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
-    public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (mService != null) {
-            try {
-                mService.setActivePasswordState(metrics, userHandle);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-    }
-
-    /**
-     * @hide
-     */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5cdef6d..0da5b7a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -131,7 +131,6 @@
     void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle);
     boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
 
-    void setActivePasswordState(in PasswordMetrics metrics, int userHandle);
     void reportPasswordChanged(int userId);
     void reportFailedPasswordAttempt(int userHandle);
     void reportSuccessfulPasswordAttempt(int userHandle);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 86e768d..f9e710e 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1080,6 +1080,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdPackage() {
             return mIdPackage;
         }
@@ -1089,6 +1090,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdType() {
             return mIdType;
         }
@@ -1098,6 +1100,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdEntry() {
             return mIdEntry;
         }
@@ -1391,6 +1394,7 @@
          * For example, a button will report "android.widget.Button" meaning it behaves
          * like a {@link android.widget.Button}.
          */
+        @Nullable
         public String getClassName() {
             return mClassName;
         }
@@ -1399,6 +1403,7 @@
          * Returns any content description associated with the node, which semantically describes
          * its purpose for accessibility and other uses.
          */
+        @Nullable
         public CharSequence getContentDescription() {
             return mContentDescription;
         }
@@ -1473,6 +1478,7 @@
          * Returns any text associated with the node that is displayed to the user, or null
          * if there is none.
          */
+        @Nullable
         public CharSequence getText() {
             return mText != null ? mText.mText : null;
         }
@@ -1560,6 +1566,7 @@
          * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
          * not for autofill purposes.
          */
+        @Nullable
         public int[] getTextLineCharOffsets() {
             return mText != null ? mText.mLineCharOffsets : null;
         }
@@ -1573,6 +1580,7 @@
          * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
          * not for autofill purposes.
          */
+        @Nullable
         public int[] getTextLineBaselines() {
             return mText != null ? mText.mLineBaselines : null;
         }
@@ -1592,6 +1600,7 @@
          * Return additional hint text associated with the node; this is typically used with
          * a node that takes user input, describing to the user what the input means.
          */
+        @Nullable
         public String getHint() {
             return mText != null ? mText.mHint : null;
         }
@@ -1610,6 +1619,7 @@
         /**
          * Return a Bundle containing optional vendor-specific extension information.
          */
+        @Nullable
         public Bundle getExtras() {
             return mExtras;
         }
diff --git a/core/java/android/app/backup/RestoreDescription.java b/core/java/android/app/backup/RestoreDescription.java
index 7854394..498b686 100644
--- a/core/java/android/app/backup/RestoreDescription.java
+++ b/core/java/android/app/backup/RestoreDescription.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -52,6 +53,7 @@
     /** This package's restore data is a tarball-type full data stream */
     public static final int TYPE_FULL_STREAM = 2;
 
+    @NonNull
     @Override
     public String toString() {
         return "RestoreDescription{" + mPackageName + " : "
diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java
index 298b003..d14238b 100644
--- a/core/java/android/app/prediction/AppPredictionContext.java
+++ b/core/java/android/app/prediction/AppPredictionContext.java
@@ -90,7 +90,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == this) return true;
         if (!getClass().equals(o != null ? o.getClass() : null)) return false;
 
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
index 281a16f..e5e06f8 100644
--- a/core/java/android/app/prediction/AppPredictionSessionId.java
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -16,6 +16,7 @@
 package android.app.prediction;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -46,7 +47,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!getClass().equals(o != null ? o.getClass() : null)) return false;
 
         AppPredictionSessionId other = (AppPredictionSessionId) o;
diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java
index 147c500..6f21490 100644
--- a/core/java/android/app/prediction/AppTarget.java
+++ b/core/java/android/app/prediction/AppTarget.java
@@ -151,7 +151,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!getClass().equals(o != null ? o.getClass() : null)) return false;
 
         AppTarget other = (AppTarget) o;
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 54b9563..26ff0c1 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -98,7 +98,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!getClass().equals(o != null ? o.getClass() : null)) return false;
 
         AppTargetEvent other = (AppTargetEvent) o;
diff --git a/core/java/android/app/prediction/AppTargetId.java b/core/java/android/app/prediction/AppTargetId.java
index 3603f5f..052fdc1 100644
--- a/core/java/android/app/prediction/AppTargetId.java
+++ b/core/java/android/app/prediction/AppTargetId.java
@@ -16,6 +16,7 @@
 package android.app.prediction;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -59,7 +60,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!getClass().equals(o != null ? o.getClass() : null)) return false;
 
         AppTargetId other = (AppTargetId) o;
diff --git a/core/java/android/app/role/RoleControllerService.java b/core/java/android/app/role/RoleControllerService.java
index 85db6a4..06623f9 100644
--- a/core/java/android/app/role/RoleControllerService.java
+++ b/core/java/android/app/role/RoleControllerService.java
@@ -279,6 +279,10 @@
     /**
      * Check whether an application is visible for a role.
      *
+     * While an application can be qualified for a role, it can still stay hidden from user (thus
+     * not visible). If an application is visible for a role, we may show things related to the role
+     * for it, e.g. showing an entry pointing to the role settings in its application info page.
+     *
      * @param roleName name of the role to check for
      * @param packageName package name of the application to check for
      *
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index b92d538..b5aed49f 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -81,7 +81,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o instanceof CacheQuotaHint) {
             final CacheQuotaHint other = (CacheQuotaHint) o;
             return Objects.equals(mUuid, other.mUuid)
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index b564c31..5dbca12 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -276,10 +276,24 @@
         public static final int DEVICE_STARTUP = 27;
 
         /**
+         * An event type denoting that a user has been unlocked for the first time. This event
+         * mainly indicates when the user's credential encrypted storage was first accessible.
+         * @hide
+         */
+        public static final int USER_UNLOCKED = 28;
+
+        /**
+         * An event type denoting that a user has been stopped. This typically happens when the
+         * system is being turned off or when users are being switched.
+         * @hide
+         */
+        public static final int USER_STOPPED = 29;
+
+        /**
          * Keep in sync with the greatest event type value.
          * @hide
          */
-        public static final int MAX_EVENT_TYPE = 27;
+        public static final int MAX_EVENT_TYPE = 29;
 
         /** @hide */
         public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
@@ -554,6 +568,7 @@
          * event is of type {@link #NOTIFICATION_INTERRUPTION}, otherwise it returns null;
          * @hide
          */
+        @Nullable
         @SystemApi
         public String getNotificationChannelId() {
             return mNotificationChannelId;
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 749a011..656f474 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -31,6 +31,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 
 import java.lang.annotation.Retention;
@@ -290,6 +291,9 @@
      * </p>
      *
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -324,6 +328,9 @@
      * the specified interval. The results are ordered as in
      * {@link #queryUsageStats(int, long, long)}.
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -362,6 +369,9 @@
      * </ul>
      *
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -395,6 +405,9 @@
      * Query for events in the given time range. Events are only kept by the system for a few
      * days.
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
      *                 Defined in terms of "Unix time", see
@@ -418,6 +431,9 @@
 
     /**
      * Like {@link #queryEvents(long, long)}, but only returns events for the calling package.
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
      *                 Defined in terms of "Unix time", see
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index bbec6b3..b3260c4 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.os.UserHandle;
+import android.os.UserManager;
 
 import java.util.List;
 import java.util.Set;
@@ -33,7 +34,10 @@
 public abstract class UsageStatsManagerInternal {
 
     /**
-     * Reports an event to the UsageStatsManager.
+     * Reports an event to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param component The component for which this event occurred.
      * @param userId The user id to which the component belongs to.
@@ -48,7 +52,10 @@
             int instanceId, ComponentName taskRoot);
 
     /**
-     * Reports an event to the UsageStatsManager.
+     * Reports an event to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package for which this event occurred.
      * @param userId The user id to which the component belongs to.
@@ -58,14 +65,20 @@
     public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);
 
     /**
-     * Reports a configuration change to the UsageStatsManager.
+     * Reports a configuration change to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param config The new device configuration.
      */
     public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
 
     /**
-     * Reports that an application has posted an interruptive notification.
+     * Reports that an application has posted an interruptive notification. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package name of the app that posted the notification
      * @param channelId The ID of the NotificationChannel to which the notification was posted
@@ -75,7 +88,10 @@
             @UserIdInt int userId);
 
     /**
-     * Reports that an action equivalent to a ShortcutInfo is taken by the user.
+     * Reports that an action equivalent to a ShortcutInfo is taken by the user. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package name of the shortcut publisher
      * @param shortcutId The ID of the shortcut in question
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index f003d4b..c20cb25 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -56,6 +56,7 @@
     static final int HANDLE_PROVIDERS_CHANGED = 3;
     @UnsupportedAppUsage
     static final int HANDLE_VIEW_DATA_CHANGED = 4;
+    static final int HANDLE_APP_WIDGET_REMOVED = 5;
 
     final static Object sServiceLock = new Object();
     @UnsupportedAppUsage
@@ -103,6 +104,14 @@
             msg.sendToTarget();
         }
 
+        public void appWidgetRemoved(int appWidgetId) {
+            Handler handler = mWeakHandler.get();
+            if (handler == null) {
+                return;
+            }
+            handler.obtainMessage(HANDLE_APP_WIDGET_REMOVED, appWidgetId, 0).sendToTarget();
+        }
+
         public void providersChanged() {
             Handler handler = mWeakHandler.get();
             if (handler == null) {
@@ -137,6 +146,10 @@
                     updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
                     break;
                 }
+                case HANDLE_APP_WIDGET_REMOVED: {
+                    dispatchOnAppWidgetRemoved(msg.arg1);
+                    break;
+                }
                 case HANDLE_PROVIDER_CHANGED: {
                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
                     break;
@@ -224,6 +237,10 @@
                     break;
                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
                     viewDataChanged(update.appWidgetId, update.viewId);
+                    break;
+                case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED:
+                    dispatchOnAppWidgetRemoved(update.appWidgetId);
+                    break;
             }
         }
     }
@@ -426,6 +443,21 @@
         }
     }
 
+    void dispatchOnAppWidgetRemoved(int appWidgetId) {
+        synchronized (mViews) {
+            mViews.remove(appWidgetId);
+        }
+        onAppWidgetRemoved(appWidgetId);
+    }
+
+    /**
+     * Called when the app widget is removed for appWidgetId
+     * @param appWidgetId
+     */
+    public void onAppWidgetRemoved(int appWidgetId) {
+        // Does nothing
+    }
+
     /**
      * Called when the set of available widgets changes (ie. widget containing packages
      * are added, updated or removed, or widget components are enabled or disabled.)
diff --git a/core/java/android/appwidget/PendingHostUpdate.java b/core/java/android/appwidget/PendingHostUpdate.java
index e1bf159..1f1d644 100644
--- a/core/java/android/appwidget/PendingHostUpdate.java
+++ b/core/java/android/appwidget/PendingHostUpdate.java
@@ -28,6 +28,7 @@
     static final int TYPE_VIEWS_UPDATE = 0;
     static final int TYPE_PROVIDER_CHANGED = 1;
     static final int TYPE_VIEW_DATA_CHANGED = 2;
+    static final int TYPE_APP_WIDGET_REMOVED = 3;
 
     final int appWidgetId;
     final int type;
@@ -53,6 +54,13 @@
         return update;
     }
 
+    /**
+     * IAppWidgetHost appWidgetRemoved implimentaion
+     */
+    public static PendingHostUpdate appWidgetRemoved(int appWidgetId) {
+        return new PendingHostUpdate(appWidgetId, TYPE_APP_WIDGET_REMOVED);
+    }
+
     private PendingHostUpdate(int appWidgetId, int type) {
         this.appWidgetId = appWidgetId;
         this.type = type;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 39d63de..e7ba85a 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -858,7 +858,10 @@
         if (DBG) {
             Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state));
         }
-        return (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON);
+        return (state == BluetoothAdapter.STATE_ON
+                || state == BluetoothAdapter.STATE_BLE_ON
+                || state == BluetoothAdapter.STATE_TURNING_ON
+                || state == BluetoothAdapter.STATE_TURNING_OFF);
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java
index dfec4e1..a0aa2de 100644
--- a/core/java/android/bluetooth/BluetoothOutputStream.java
+++ b/core/java/android/bluetooth/BluetoothOutputStream.java
@@ -75,16 +75,4 @@
         }
         mSocket.write(b, offset, count);
     }
-
-    /**
-     * Wait until the data in sending queue is emptied. A polling version
-     * for flush implementation. Use it to ensure the writing data afterwards will
-     * be packed in the new RFCOMM frame.
-     *
-     * @throws IOException if an i/o error occurs.
-     * @since Android 4.2.3
-     */
-    public void flush() throws IOException {
-        mSocket.flush();
-    }
 }
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index a6e3153..760166b 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -515,20 +515,6 @@
         return mSocketIS.available();
     }
 
-    /**
-     * Wait until the data in sending queue is emptied. A polling version
-     * for flush implementation. Used to ensure the writing data afterwards will
-     * be packed in new RFCOMM frame.
-     *
-     * @throws IOException if an i/o error occurs.
-     */
-    @UnsupportedAppUsage
-    /*package*/ void flush() throws IOException {
-        if (mSocketOS == null) throw new IOException("flush is called on null OutputStream");
-        if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
-        mSocketOS.flush();
-    }
-
     /*package*/ int read(byte[] b, int offset, int length) throws IOException {
         int ret = 0;
         if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java
index 082663e..97b33b7 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -97,6 +97,8 @@
 
     /**
      * Returns if autofill is disabled by service to the given activity.
+     *
+     * @hide
      */
     public boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
         final long elapsedTime = SystemClock.elapsedRealtime();
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 621f331..5c2de57 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -684,10 +684,26 @@
             return new ContentProviderOperation(this);
         }
 
-        private void setValue(@NonNull String key, @NonNull Object value) {
+        private void ensureValues() {
             if (mValues == null) {
                 mValues = new ArrayMap<>();
             }
+        }
+
+        private void ensureExtras() {
+            if (mExtras == null) {
+                mExtras = new ArrayMap<>();
+            }
+        }
+
+        private void ensureSelectionArgs() {
+            if (mSelectionArgs == null) {
+                mSelectionArgs = new SparseArray<>();
+            }
+        }
+
+        private void setValue(@NonNull String key, @NonNull Object value) {
+            ensureValues();
             final boolean oldReference = mValues.get(key) instanceof BackReference;
             final boolean newReference = value instanceof BackReference;
             if (!oldReference || newReference) {
@@ -696,9 +712,7 @@
         }
 
         private void setExtra(@NonNull String key, @NonNull Object value) {
-            if (mExtras == null) {
-                mExtras = new ArrayMap<>();
-            }
+            ensureExtras();
             final boolean oldReference = mExtras.get(key) instanceof BackReference;
             final boolean newReference = value instanceof BackReference;
             if (!oldReference || newReference) {
@@ -707,9 +721,7 @@
         }
 
         private void setSelectionArg(int index, @NonNull Object value) {
-            if (mSelectionArgs == null) {
-                mSelectionArgs = new SparseArray<>();
-            }
+            ensureSelectionArgs();
             final boolean oldReference = mSelectionArgs.get(index) instanceof BackReference;
             final boolean newReference = value instanceof BackReference;
             if (!oldReference || newReference) {
@@ -728,6 +740,7 @@
          */
         public @NonNull Builder withValues(@NonNull ContentValues values) {
             assertValuesAllowed();
+            ensureValues();
             final ArrayMap<String, Object> rawValues = values.getValues();
             for (int i = 0; i < rawValues.size(); i++) {
                 setValue(rawValues.keyAt(i), rawValues.valueAt(i));
@@ -815,6 +828,7 @@
          */
         public @NonNull Builder withExtras(@NonNull Bundle extras) {
             assertExtrasAllowed();
+            ensureExtras();
             for (String key : extras.keySet()) {
                 setExtra(key, extras.get(key));
             }
@@ -885,6 +899,7 @@
             assertSelectionAllowed();
             mSelection = selection;
             if (selectionArgs != null) {
+                ensureSelectionArgs();
                 for (int i = 0; i < selectionArgs.length; i++) {
                     setSelectionArg(i, selectionArgs[i]);
                 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5dcc291..802c1a0a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -854,11 +854,16 @@
      * to any callers for the same name, meaning they will see each other's
      * edits as soon as they are made.
      *
-     * This method is thead-safe.
+     * <p>This method is thread-safe.
      *
-     * @param name Desired preferences file. If a preferences file by this name
-     * does not exist, it will be created when you retrieve an
-     * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+     * <p>If the preferences directory does not already exist, it will be created when this method
+     * is called.
+     *
+     * <p>If a preferences file by this name does not exist, it will be created when you retrieve an
+     * editor ({@link SharedPreferences#edit()}) and then commit changes ({@link
+     * SharedPreferences.Editor#commit()} or {@link SharedPreferences.Editor#apply()}).
+     *
+     * @param name Desired preferences file.
      * @param mode Operating mode.
      *
      * @return The single {@link SharedPreferences} instance that can be used
@@ -4703,6 +4708,14 @@
     public static final String DYNAMIC_SYSTEM_SERVICE = "dynamic_system";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.telephony.TelephonyRegistryManager}.
+     * @hide
+     */
+    @SystemApi
+    public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2a17800..3418b7b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5413,8 +5413,8 @@
             "android.intent.extra.ALLOW_MULTIPLE";
 
     /**
-     * The integer userHandle carried with broadcast intents related to addition, removal and
-     * switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+     * The integer userHandle (i.e. userId) carried with broadcast intents related to addition,
+     * removal and switching of users and managed profiles - {@link #ACTION_USER_ADDED},
      * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
      *
      * @hide
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 9d52363..099dea2 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -60,9 +60,7 @@
  * multiple possible matching values (via {@link #addAction},
  * {@link #addDataType}, {@link #addDataScheme}, {@link #addDataSchemeSpecificPart},
  * {@link #addDataAuthority}, {@link #addDataPath}, and {@link #addCategory}, respectively).
- * For actions, the field
- * will not be tested if no values have been given (treating it as a wildcard);
- * if no data characteristics are specified, however, then the filter will
+ * For actions, if no data characteristics are specified, then the filter will
  * only match intents that contain no data.
  *
  * <p>The data characteristic is
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 6fe6e99..e24512a 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import static android.app.AppOpsManager.strOpToOp;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,6 +77,16 @@
     }
 
     /**
+     * @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkPermission(@NonNull Context context, @NonNull String permission,
+            int pid, int uid, @Nullable String packageName) {
+        return checkPermission(context, permission, pid, uid, packageName, null);
+    }
+
+    /**
      * Checks whether a given package in a UID and PID has a given permission
      * and whether the app op that corresponds to this permission is allowed.
      *
@@ -84,12 +96,14 @@
      * @param uid The uid for which to check.
      * @param packageName The package name for which to check. If null the
      *     the first package for the calling UID will be used.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkPermission(@NonNull Context context, @NonNull String permission,
-            int pid, int uid, @Nullable String packageName) {
+            int pid, int uid, @Nullable String packageName, @Nullable String message) {
         if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
             return PERMISSION_DENIED;
         }
@@ -108,7 +122,8 @@
             packageName = packageNames[0];
         }
 
-        if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid) != AppOpsManager.MODE_ALLOWED) {
+        if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message)
+                != AppOpsManager.MODE_ALLOWED) {
             return PERMISSION_DENIED_APP_OP;
         }
 
@@ -131,7 +146,17 @@
     public static int checkSelfPermission(@NonNull Context context,
             @NonNull String permission) {
         return checkPermission(context, permission, Process.myPid(),
-                Process.myUid(), context.getPackageName());
+                Process.myUid(), context.getPackageName(), null /* self access */);
+    }
+
+    /**
+     * @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkCallingPermission(@NonNull Context context,
+            @NonNull String permission, @Nullable String packageName) {
+        return checkCallingPermission(context, permission, packageName, null);
     }
 
     /**
@@ -142,17 +167,29 @@
      * @param permission The permission to check.
      * @param packageName The package name making the IPC. If null the
      *     the first package for the calling UID will be used.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkCallingPermission(@NonNull Context context,
-            @NonNull String permission, @Nullable String packageName) {
+            @NonNull String permission, @Nullable String packageName, @Nullable String message) {
         if (Binder.getCallingPid() == Process.myPid()) {
             return PERMISSION_DENIED;
         }
         return checkPermission(context, permission, Binder.getCallingPid(),
-                Binder.getCallingUid(), packageName);
+                Binder.getCallingUid(), packageName, message);
+    }
+
+    /**
+     * @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkCallingOrSelfPermission(@NonNull Context context,
+            @NonNull String permission) {
+        return checkCallingOrSelfPermission(context, permission, null);
     }
 
     /**
@@ -161,15 +198,17 @@
      *
      * @param context Context for accessing resources.
      * @param permission The permission to check.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkCallingOrSelfPermission(@NonNull Context context,
-            @NonNull String permission) {
+            @NonNull String permission, @Nullable String message) {
         String packageName = (Binder.getCallingPid() == Process.myPid())
                 ? context.getPackageName() : null;
         return checkPermission(context, permission, Binder.getCallingPid(),
-                Binder.getCallingUid(), packageName);
+                Binder.getCallingUid(), packageName, message);
     }
 }
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 9d87e25..c193868 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -16,7 +16,6 @@
 
 package android.content;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import java.util.Map;
@@ -58,33 +57,18 @@
          *
          * <p>This callback will be run on your main thread.
          *
-         * <p><em>Note: This callback will not be triggered when preferences are cleared via
-         * {@link Editor#clear()}. However, from {@link android.os.Build.VERSION_CODES#R Android R}
-         * onwards, you can use {@link OnSharedPreferencesClearListener} to register for
-         * {@link Editor#clear()} callbacks.</em>
-         *
-         * @param sharedPreferences The {@link SharedPreferences} that received
-         *            the change.
-         * @param key The key of the preference that was changed, added, or
-         *            removed.
-         */
-        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when shared preferences are cleared.
-     */
-    public interface OnSharedPreferencesClearListener {
-        /**
-         * Called when shared preferences are cleared via {@link Editor#clear()}.
-         *
-         * <p>This callback will be run on your main thread.
+         * <p><em>Note: This callback will not be triggered when preferences are cleared
+         * via {@link Editor#clear()}, unless targeting {@link android.os.Build.VERSION_CODES#R}
+         * on devices running OS versions {@link android.os.Build.VERSION_CODES#R Android R}
+         * or later.</em>
          *
          * @param sharedPreferences The {@link SharedPreferences} that received the change.
-         * @param keys The set of keys that were cleared.
+         * @param key The key of the preference that was changed, added, or removed. Apps targeting
+         *            {@link android.os.Build.VERSION_CODES#R} on devices running OS versions
+         *            {@link android.os.Build.VERSION_CODES#R Android R} or later, will receive
+         *            a {@code null} value when preferences are cleared.
          */
-        void onSharedPreferencesClear(@NonNull SharedPreferences sharedPreferences,
-                @NonNull Set<String> keys);
+        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
     }
 
     /**
@@ -405,35 +389,4 @@
      * @see #registerOnSharedPreferenceChangeListener
      */
     void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
-
-    /**
-     * Registers a callback to be invoked when preferences are cleared via {@link Editor#clear()}.
-     *
-     * <p class="caution"><strong>Caution:</strong> The preference manager does
-     * not currently store a strong reference to the listener. You must store a
-     * strong reference to the listener, or it will be susceptible to garbage
-     * collection. We recommend you keep a reference to the listener in the
-     * instance data of an object that will exist as long as you need the
-     * listener.</p>
-     *
-     * @param listener The callback that will run.
-     * @see #unregisterOnSharedPreferencesClearListener
-     */
-    default void registerOnSharedPreferencesClearListener(
-            @NonNull OnSharedPreferencesClearListener listener) {
-        throw new UnsupportedOperationException(
-                "registerOnSharedPreferencesClearListener not implemented");
-    }
-
-    /**
-     * Unregisters a previous callback for {@link Editor#clear()}.
-     *
-     * @param listener The callback that should be unregistered.
-     * @see #registerOnSharedPreferencesClearListener
-     */
-    default void unregisterOnSharedPreferencesClearListener(
-            @NonNull OnSharedPreferencesClearListener listener) {
-        throw new UnsupportedOperationException(
-                "unregisterOnSharedPreferencesClearListener not implemented");
-    }
 }
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index f39fc66..1a78f79 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -413,7 +413,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -448,6 +448,7 @@
         return true;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
diff --git a/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java b/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
new file mode 100644
index 0000000..1a720d5
--- /dev/null
+++ b/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import static android.content.pm.SharedLibraryNames.ANDROID_TELEPHONY_COMMON;
+
+
+import com.android.internal.compat.IPlatformCompat;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.PackageParser.Package;
+
+import android.os.Build.VERSION_CODES;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Updates a package to ensure that
+ * <ul>
+ * <li> if apps have target SDK < R, then telephony-common library is included by default to
+ * their class path. Even without <uses-library>.</li>
+ * <li> if apps with target SDK level >= R && have special permission (or Phone UID):
+ * apply <uses-library> on telephony-common should work.</li>
+ * <li> Otherwise not allow to use the lib.
+ * See {@link PackageSharedLibraryUpdater#removeLibrary(Package, String)}.</li>
+ * </ul>
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class AndroidTelephonyCommonUpdater extends PackageSharedLibraryUpdater {
+
+    private static final String TAG = AndroidTelephonyCommonUpdater.class.getSimpleName();
+    /**
+     * Restrict telephony-common lib for apps having target SDK >= R
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.Q)
+    static final long RESTRICT_TELEPHONY_COMMON_CHANGE_ID = 139318877L;
+
+    private static boolean apkTargetsApiLevelLessThanROrCurrent(Package pkg) {
+        boolean shouldRestrict = false;
+        try {
+            IBinder b = ServiceManager.getService("platform_compat");
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(b);
+            shouldRestrict = platformCompat.isChangeEnabled(RESTRICT_TELEPHONY_COMMON_CHANGE_ID,
+                pkg.applicationInfo);
+        } catch (RemoteException ex) {
+            Log.e(TAG, ex.getMessage());
+        }
+        // TODO(b/139318877): remove version check for CUR_DEVELOPEMENT after clean up work.
+        return !shouldRestrict
+            || pkg.applicationInfo.targetSdkVersion == VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
+    @Override
+    public void updatePackage(Package pkg) {
+        // for apps with targetSDKVersion < R include the library for backward compatibility.
+        if (apkTargetsApiLevelLessThanROrCurrent(pkg)) {
+            prefixRequiredLibrary(pkg, ANDROID_TELEPHONY_COMMON);
+        } else if (pkg.mSharedUserId == null || !pkg.mSharedUserId.equals("android.uid.phone")) {
+            // if apps target >= R
+            removeLibrary(pkg, ANDROID_TELEPHONY_COMMON);
+        }
+    }
+}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 037a149..3933e81 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -503,9 +503,38 @@
     }
 
     /**
-     * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
-     * {@link Intent#CATEGORY_LAUNCHER}, for a specified user. Result may include
-     * synthesized activities like app details Activity injected by system.
+     * Retrieves a list of activities that specify {@link Intent#ACTION_MAIN} and
+     * {@link Intent#CATEGORY_LAUNCHER}, across all apps, for a specified user. If an app doesn't
+     * have any activities that specify <code>ACTION_MAIN</code> or <code>CATEGORY_LAUNCHER</code>,
+     * the system adds a synthesized activity to the list. This synthesized activity represents the
+     * app's details page within system settings.
+     *
+     * <p class="note"><b>Note: </b>It's possible for system apps, such as app stores, to prevent
+     * the system from adding synthesized activities to the returned list.</p>
+     *
+     * <p>As of <a href="/reference/android/os/Build.VERSION_CODES.html#Q">Android Q</a>, at least
+     * one of the app's activities or synthesized activities appears in the returned list unless the
+     * app satisfies at least one of the following conditions:</p>
+     * <ul>
+     * <li>The app is a system app.</li>
+     * <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
+     * </li>
+     * <li>The app doesn't have a <em>launcher activity</em> that is enabled by default. A launcher
+     * activity has an intent containing the <code>ACTION_MAIN</code> action and the
+     * <code>CATEGORY_LAUNCHER</code> category.</li>
+     * </ul>
+     *
+     * <p>Additionally, the system hides synthesized activities for some or all apps in the
+     * following enterprise-related cases:</p>
+     * <ul>
+     * <li>If the device is a
+     * <a href="https://developers.google.com/android/work/overview#company-owned-devices-for-knowledge-workers">fully
+     * managed device</a>, no synthesized activities for any app appear in the returned list.</li>
+     * <li>If the current user has a
+     * <a href="https://developers.google.com/android/work/overview#employee-owned-devices-byod">work
+     * profile</a>, no synthesized activities for the user's work apps appear in the returned
+     * list.</li>
+     * </ul>
      *
      * @param packageName The specific package to query. If null, it checks all installed packages
      *            in the profile.
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 4331bd4..797ba64b 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -51,6 +51,8 @@
 
         packageUpdaters.add(new AndroidHidlUpdater());
 
+        packageUpdaters.add(new AndroidTelephonyCommonUpdater());
+
         // Add this before adding AndroidTestBaseUpdater so that android.test.base comes before
         // android.test.mock.
         packageUpdaters.add(new AndroidTestRunnerSplitUpdater());
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c561013..6d88fea 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -39,7 +39,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.StorageStatsManager;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Disabled;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -3078,8 +3078,11 @@
      * because the app was updated to support runtime permissions, the
      * the permission will be revoked in the upgrade process.
      *
+     * @deprecated Renamed to {@link #FLAG_PERMISSION_REVOKED_COMPAT}.
+     *
      * @hide
      */
+    @Deprecated
     @SystemApi
     @TestApi
     public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE =  1 << 3;
@@ -3202,6 +3205,18 @@
     public static final int FLAG_PERMISSION_GRANTED_BY_ROLE =  1 << 15;
 
     /**
+     * Permission flag: The permission should have been revoked but is kept granted for
+     * compatibility. The data protected by the permission should be protected by a no-op (empty
+     * list, default error, etc) instead of crashing the client. The permission will be revoked if
+     * the app is upgraded to supports it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int FLAG_PERMISSION_REVOKED_COMPAT =  FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
+    /**
      * Permission flags: Bitwise or of all permission flags allowing an
      * exemption for a restricted permission.
      * @hide
@@ -3241,7 +3256,8 @@
             | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
             | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
             | FLAG_PERMISSION_APPLY_RESTRICTION
-            | FLAG_PERMISSION_GRANTED_BY_ROLE;
+            | FLAG_PERMISSION_GRANTED_BY_ROLE
+            | FLAG_PERMISSION_REVOKED_COMPAT;
 
     /**
      * Injected activity in app that forwards user to setting activity of that app.
@@ -3379,7 +3395,7 @@
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    @Disabled
     public static final long FILTER_APPLICATION_QUERY = 135549675L;
 
     /** {@hide} */
@@ -4017,7 +4033,8 @@
             FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT,
             FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT,
             FLAG_PERMISSION_APPLY_RESTRICTION,
-            FLAG_PERMISSION_GRANTED_BY_ROLE
+            FLAG_PERMISSION_GRANTED_BY_ROLE,
+            FLAG_PERMISSION_REVOKED_COMPAT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionFlags {}
@@ -7086,7 +7103,6 @@
             case FLAG_PERMISSION_POLICY_FIXED: return "POLICY_FIXED";
             case FLAG_PERMISSION_SYSTEM_FIXED: return "SYSTEM_FIXED";
             case FLAG_PERMISSION_USER_SET: return "USER_SET";
-            case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
             case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
             case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
             case FLAG_PERMISSION_REVOKE_WHEN_REQUESTED: return "REVOKE_WHEN_REQUESTED";
@@ -7097,6 +7113,7 @@
             case FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT: return "RESTRICTION_UPGRADE_EXEMPT";
             case FLAG_PERMISSION_APPLY_RESTRICTION: return "APPLY_RESTRICTION";
             case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
+            case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
             default: return Integer.toString(flag);
         }
     }
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index de61d70..3e4649f 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.AppIdInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -321,25 +322,24 @@
             Bundle verificationBundle, int userId);
 
     /**
-     * Grants access to the package metadata for an ephemeral application.
+     * Grants implicit access based on an interaction between two apps. This grants the target app
+     * access to the calling application's package metadata.
      * <p>
-     * When an ephemeral application explicitly tries to interact with a full
-     * install application [via an activity, service or provider that has been
-     * exposed using the {@code visibleToInstantApp} attribute], the normal
-     * application must be able to see metadata about the connecting ephemeral
-     * app. If the ephemeral application uses an implicit intent [ie action VIEW,
-     * category BROWSABLE], it remains hidden from the launched activity.
+     * When an application explicitly tries to interact with another application [via an
+     * activity, service or provider that is either declared in the caller's
+     * manifest via the {@code <queries>} tag or has been exposed via the target apps manifest using
+     * the {@code visibleToInstantApp} attribute], the target application must be able to see
+     * metadata about the calling app. If the calling application uses an implicit intent [ie
+     * action VIEW, category BROWSABLE], it remains hidden from the launched app.
      * <p>
-     * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
-     * is not for a fully installed app, this method will be a no-op.
-     *
      * @param userId the user
      * @param intent the intent that triggered the grant
-     * @param targetAppId The app ID of the fully installed application
-     * @param ephemeralAppId The app ID of the ephemeral application
+     * @param callingUid The uid of the calling application
+     * @param targetAppId The app ID of the target application
      */
-    public abstract void grantEphemeralAccess(int userId, Intent intent,
-            int targetAppId, int ephemeralAppId);
+    public abstract void grantImplicitAccess(
+            @UserIdInt int userId, Intent intent, int callingUid,
+            @AppIdInt int targetAppId);
 
     public abstract boolean isInstantAppInstallerComponent(ComponentName component);
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ef08bf5..0b157fa 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -57,6 +57,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageParserCacheHelper.ReadHelper;
 import android.content.pm.PackageParserCacheHelper.WriteHelper;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
@@ -79,7 +80,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.permission.PermissionManager;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.system.StructStat;
@@ -2528,11 +2528,17 @@
             Slog.i(TAG, newPermsMsg.toString());
         }
 
+        List<SplitPermissionInfoParcelable> splitPermissions;
 
-        final int NS = PermissionManager.SPLIT_PERMISSIONS.size();
-        for (int is=0; is<NS; is++) {
-            final PermissionManager.SplitPermissionInfo spi =
-                    PermissionManager.SPLIT_PERMISSIONS.get(is);
+        try {
+            splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        final int listSize = splitPermissions.size();
+        for (int is = 0; is < listSize; is++) {
+            final SplitPermissionInfoParcelable spi = splitPermissions.get(is);
             if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk()
                     || !pkg.requestedPermissions.contains(spi.getSplitPermission())) {
                 continue;
@@ -4064,32 +4070,54 @@
                         intentInfo, outError)) {
                     return false;
                 }
-                Intent intent = new Intent();
-                if (intentInfo.countActions() != 1) {
-                    outError[0] = "intent tags must contain exactly one action.";
-                    return false;
-                }
-                intent.setAction(intentInfo.getAction(0));
-                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
-                    intent.addCategory(intentInfo.getCategory(i));
-                }
+
                 Uri data = null;
                 String dataType = null;
-                if (intentInfo.countDataTypes() > 1) {
+                String host = "";
+                final int numActions = intentInfo.countActions();
+                final int numSchemes = intentInfo.countDataSchemes();
+                final int numTypes = intentInfo.countDataTypes();
+                final int numHosts = intentInfo.getHosts().length;
+                if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
+                    outError[0] = "intent tags must contain either an action or data.";
+                    return false;
+                }
+                if (numActions > 1) {
+                    outError[0] = "intent tag may have at most one action.";
+                    return false;
+                }
+                if (numTypes > 1) {
                     outError[0] = "intent tag may have at most one data type.";
                     return false;
                 }
-                if (intentInfo.countDataSchemes() > 1) {
+                if (numSchemes > 1) {
                     outError[0] = "intent tag may have at most one data scheme.";
                     return false;
                 }
-                if (intentInfo.countDataTypes() == 1) {
-                    data = Uri.fromParts(intentInfo.getDataType(0), "", null);
+                if (numHosts > 1) {
+                    outError[0] = "intent tag may have at most one data host.";
+                    return false;
                 }
-                if (intentInfo.countDataSchemes() == 1) {
-                    dataType = intentInfo.getDataScheme(0);
+                Intent intent = new Intent();
+                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
+                    intent.addCategory(intentInfo.getCategory(i));
+                }
+                if (numHosts == 1) {
+                    host = intentInfo.getHosts()[0];
+                }
+                if (numSchemes == 1) {
+                    data = new Uri.Builder()
+                            .scheme(intentInfo.getDataScheme(0))
+                            .authority(host)
+                            .build();
+                }
+                if (numTypes == 1) {
+                    dataType = intentInfo.getDataType(0);
                 }
                 intent.setDataAndType(data, dataType);
+                if (numActions == 1) {
+                    intent.setAction(intentInfo.getAction(0));
+                }
                 owner.mQueriesIntents = ArrayUtils.add(owner.mQueriesIntents, intent);
             } else if (parser.getName().equals("package")) {
                 final TypedArray sa = res.obtainAttributes(parser,
@@ -8501,5 +8529,4 @@
             this.error = error;
         }
     }
-
 }
diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java
index a607a9f..4c66fc0 100644
--- a/core/java/android/content/pm/SharedLibraryNames.java
+++ b/core/java/android/content/pm/SharedLibraryNames.java
@@ -33,4 +33,6 @@
     static final String ANDROID_TEST_RUNNER = "android.test.runner";
 
     public static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy";
+
+    public static final String ANDROID_TELEPHONY_COMMON = "telephony-common";
 }
diff --git a/core/java/android/content/pm/SuspendDialogInfo.java b/core/java/android/content/pm/SuspendDialogInfo.java
index db8f8c2..73b75df 100644
--- a/core/java/android/content/pm/SuspendDialogInfo.java
+++ b/core/java/android/content/pm/SuspendDialogInfo.java
@@ -185,7 +185,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -200,6 +200,7 @@
                 && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage);
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder builder = new StringBuilder("SuspendDialogInfo: {");
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d03bea2..e65d761 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -57,7 +57,7 @@
 
     /**
      * Primary user. Only one user can have this flag set. It identifies the first human user
-     * on a device.
+     * on a device. This flag is not supported in headless system user mode.
      */
     @UnsupportedAppUsage
     public static final int FLAG_PRIMARY = 0x00000001;
diff --git a/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl
new file mode 100644
index 0000000..d84454c
--- /dev/null
+++ b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.content.pm.permission;
+
+ parcelable SplitPermissionInfoParcelable;
\ No newline at end of file
diff --git a/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
new file mode 100644
index 0000000..421ae49
--- /dev/null
+++ b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
@@ -0,0 +1,202 @@
+/*
+ * 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 android.content.pm.permission;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Parcelable version of {@link android.permission.PermissionManager.SplitPermissionInfo}
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true)
+public class SplitPermissionInfoParcelable implements Parcelable {
+
+    /**
+     * The permission that is split.
+     */
+    @NonNull
+    private final String mSplitPermission;
+
+    /**
+     * The permissions that are added.
+     */
+    @NonNull
+    private final List<String> mNewPermissions;
+
+    /**
+     * The target API level when the permission was split.
+     */
+    @IntRange(from = 0)
+    private final int mTargetSdk;
+
+    private void onConstructed() {
+        Preconditions.checkCollectionElementsNotNull(mNewPermissions, "newPermissions");
+    }
+
+
+
+    // Code below generated by codegen v1.0.0.
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    /**
+     * Creates a new SplitPermissionInfoParcelable.
+     *
+     * @param splitPermission
+     *   The permission that is split.
+     * @param newPermissions
+     *   The permissions that are added.
+     * @param targetSdk
+     *   The target API level when the permission was split.
+     */
+    @DataClass.Generated.Member
+    public SplitPermissionInfoParcelable(
+            @NonNull String splitPermission,
+            @NonNull List<String> newPermissions,
+            @IntRange(from = 0) int targetSdk) {
+        this.mSplitPermission = splitPermission;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSplitPermission);
+        this.mNewPermissions = newPermissions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mNewPermissions);
+        this.mTargetSdk = targetSdk;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mTargetSdk,
+                "from", 0);
+
+        onConstructed();
+    }
+
+    /**
+     * The permission that is split.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getSplitPermission() {
+        return mSplitPermission;
+    }
+
+    /**
+     * The permissions that are added.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getNewPermissions() {
+        return mNewPermissions;
+    }
+
+    /**
+     * The target API level when the permission was split.
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) int getTargetSdk() {
+        return mTargetSdk;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(SplitPermissionInfoParcelable other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        SplitPermissionInfoParcelable that = (SplitPermissionInfoParcelable) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mSplitPermission, that.mSplitPermission)
+                && Objects.equals(mNewPermissions, that.mNewPermissions)
+                && mTargetSdk == that.mTargetSdk;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mSplitPermission);
+        _hash = 31 * _hash + Objects.hashCode(mNewPermissions);
+        _hash = 31 * _hash + mTargetSdk;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeString(mSplitPermission);
+        dest.writeStringList(mNewPermissions);
+        dest.writeInt(mTargetSdk);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<SplitPermissionInfoParcelable> CREATOR
+            = new Parcelable.Creator<SplitPermissionInfoParcelable>() {
+        @Override
+        public SplitPermissionInfoParcelable[] newArray(int size) {
+            return new SplitPermissionInfoParcelable[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public SplitPermissionInfoParcelable createFromParcel(Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            String splitPermission = in.readString();
+            List<String> newPermissions = new java.util.ArrayList<>();
+            in.readStringList(newPermissions);
+            int targetSdk = in.readInt();
+            return new SplitPermissionInfoParcelable(
+                    splitPermission,
+                    newPermissions,
+                    targetSdk);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1567634390477L,
+            codegenVersion = "1.0.0",
+            sourceFile = "frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mSplitPermission\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mNewPermissions\nprivate final @android.annotation.IntRange(from=0L) int mTargetSdk\nprivate  void onConstructed()\nclass SplitPermissionInfoParcelable extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 861ae7b..ac1cbd4 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -23,6 +23,7 @@
 import static android.content.ConfigurationProto.KEYBOARD;
 import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
 import static android.content.ConfigurationProto.LOCALES;
+import static android.content.ConfigurationProto.LOCALE_LIST;
 import static android.content.ConfigurationProto.MCC;
 import static android.content.ConfigurationProto.MNC;
 import static android.content.ConfigurationProto.NAVIGATION;
@@ -1111,7 +1112,7 @@
             protoOutputStream.write(MCC, mcc);
             protoOutputStream.write(MNC, mnc);
             if (mLocaleList != null) {
-                mLocaleList.writeToProto(protoOutputStream, LOCALES);
+                protoOutputStream.write(LOCALE_LIST, mLocaleList.toLanguageTags());
             }
             protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
             protoOutputStream.write(COLOR_MODE, colorMode);
@@ -1222,7 +1223,15 @@
                                                         .setVariant(variant)
                                                         .setScript(script)
                                                         .build();
-                                list.add(locale);
+                                // Log a WTF here if a repeated locale is found to avoid throwing an
+                                // exception in system server when LocaleList is created below
+                                final int inListIndex = list.indexOf(locale);
+                                if (inListIndex != -1) {
+                                    Slog.wtf(TAG, "Repeated locale (" + list.get(inListIndex) + ")"
+                                            + " found when trying to add: " + locale.toString());
+                                } else {
+                                    list.add(locale);
+                                }
                             } catch (IllformedLocaleException e) {
                                 Slog.e(TAG, "readFromProto error building locale with: "
                                         + "language-" + language + ";country-" + country
@@ -1275,6 +1284,14 @@
                     case (int) WINDOW_CONFIGURATION:
                         windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION);
                         break;
+                    case (int) LOCALE_LIST:
+                        try {
+                            setLocales(LocaleList.forLanguageTags(protoInputStream.readString(
+                                    LOCALE_LIST)));
+                        } catch (Exception e) {
+                            Slog.e(TAG, "error parsing locale list in configuration.", e);
+                        }
+                        break;
                 }
             }
         } finally {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 33e51c9..8e8a81d 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -2169,6 +2169,20 @@
     }
 
     /**
+     * Set camera audio restriction mode.
+     *
+     * @hide
+     */
+    public native final void setAudioRestriction(int mode);
+
+    /**
+     * Get currently applied camera audio restriction mode.
+     *
+     * @hide
+     */
+    public native final int getAudioRestriction();
+
+    /**
      * Image size (width and height dimensions).
      * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
      *             applications.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index bac6522..fb1ece2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -164,6 +164,37 @@
           TEMPLATE_MANUAL})
      public @interface RequestTemplate {};
 
+     /**
+      * No vibration or sound muting for this camera device. This is the default
+      * mode for all camera devices.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_NONE = 0;
+
+     /**
+      * Mute vibration from ringtones, alarms or notifications while this camera device is in use.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_VIBRATION = 1;
+
+     /**
+      * Mute vibration and sound from ringtones, alarms or notifications while this camera device is
+      * in use.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_VIBRATION_SOUND = 3;
+
+     /** @hide */
+     @Retention(RetentionPolicy.SOURCE)
+     @IntDef(prefix = {"AUDIO_RESTRICTION_"}, value =
+         {AUDIO_RESTRICTION_NONE,
+          AUDIO_RESTRICTION_VIBRATION,
+          AUDIO_RESTRICTION_VIBRATION_SOUND})
+     public @interface CAMERA_AUDIO_RESTRICTION {};
+
     /**
      * Get the ID of this camera device.
      *
@@ -536,6 +567,9 @@
      * match one of the supported input sizes for {@link android.graphics.ImageFormat#PRIVATE}
      * format. Otherwise, creating a reprocessable capture session will fail.</p>
      *
+     * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
+     * queued but not yet processed buffers from the input surface.</p>
+     *
      * <p>The guaranteed stream configurations listed in
      * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
      * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
@@ -1208,6 +1242,57 @@
     }
 
     /**
+     * Set audio restriction mode when this CameraDevice is being used.
+     *
+     * <p>Some camera hardware (e.g. devices with optical image stabilization support)
+     * are sensitive to device vibration and video recordings can be ruined by unexpected sounds.
+     * Applications can use this method to suppress vibration or sounds coming from
+     * ringtones, alarms or notifications.
+     * Other vibration or sounds (e.g. media playback or accessibility) will not be muted.</p>
+     *
+     * <p>The mute mode is a system-wide setting. When multiple CameraDevice objects
+     * are setting different modes, the system will pick a the mode that's union of
+     * all modes set by CameraDevice. Applications can also use
+     * {@link #getCameraAudioRestriction} to query current system-wide camera
+     * mute mode in effect.</p>
+     *
+     * <p>The mute settings from this CameraDevice will be automatically removed when the
+     * CameraDevice is closed or the application is disconnected from the camera.</p>
+     *
+     * @param mode An enumeration selecting the audio restriction mode for this camera device.
+     *
+     * @throws IllegalArgumentException if the mode is not supported
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #getCameraAudioRestriction
+     */
+    public void setCameraAudioRestriction(
+            @CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
+     * Get currently applied global camera audio restriction mode.
+     *
+     * <p>Application can use this method to retrieve the system-wide camera audio restriction
+     * settings described in {@link #setCameraAudioRestriction}.</p>
+     *
+     * @return The current system-wide mute mode setting in effect
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #setCameraAudioRestriction
+     */
+    public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * To be inherited by android.hardware.camera2.* code only.
      * @hide
      */
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c8276b2..fc90096 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.hardware.CameraInfo;
 import android.hardware.CameraStatus;
@@ -47,6 +48,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.RejectedExecutionException;
@@ -109,6 +111,21 @@
     }
 
     /**
+     * Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with
+     * cameraserver in order to get the list of camera ids. This is to faciliate testing since some
+     * camera ids may go 'offline' without callbacks from cameraserver because of changes in
+     * SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call
+     * adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call
+     * affects the camera ids returned by getCameraIdList() as well. Tests which do adopt shell
+     * permission identity should not mix getCameraIdList() and getCameraListNoLazyCalls().
+     */
+    /** @hide */
+    @TestApi
+    public String[] getCameraIdListNoLazy() throws CameraAccessException {
+        return CameraManagerGlobal.get().getCameraIdListNoLazy();
+    }
+
+    /**
      * Register a callback to be notified about camera device availability.
      *
      * <p>Registering the same callback again will replace the handler with the
@@ -995,35 +1012,27 @@
                 // Camera service is now down, leave mCameraService as null
             }
         }
-
-        /**
-         * Get a list of all camera IDs that are at least PRESENT; ignore devices that are
-         * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
-         */
-        public String[] getCameraIdList() {
+        private String[] extractCameraIdListLocked() {
             String[] cameraIds = null;
-            synchronized(mLock) {
-                // Try to make sure we have an up-to-date list of camera devices.
-                connectCameraServiceLocked();
-
-                int idCount = 0;
-                for (int i = 0; i < mDeviceStatus.size(); i++) {
-                    int status = mDeviceStatus.valueAt(i);
-                    if (status == ICameraServiceListener.STATUS_NOT_PRESENT ||
-                            status == ICameraServiceListener.STATUS_ENUMERATING) continue;
-                    idCount++;
-                }
-                cameraIds = new String[idCount];
-                idCount = 0;
-                for (int i = 0; i < mDeviceStatus.size(); i++) {
-                    int status = mDeviceStatus.valueAt(i);
-                    if (status == ICameraServiceListener.STATUS_NOT_PRESENT ||
-                            status == ICameraServiceListener.STATUS_ENUMERATING) continue;
-                    cameraIds[idCount] = mDeviceStatus.keyAt(i);
-                    idCount++;
-                }
+            int idCount = 0;
+            for (int i = 0; i < mDeviceStatus.size(); i++) {
+                int status = mDeviceStatus.valueAt(i);
+                if (status == ICameraServiceListener.STATUS_NOT_PRESENT
+                        || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
+                idCount++;
             }
-
+            cameraIds = new String[idCount];
+            idCount = 0;
+            for (int i = 0; i < mDeviceStatus.size(); i++) {
+                int status = mDeviceStatus.valueAt(i);
+                if (status == ICameraServiceListener.STATUS_NOT_PRESENT
+                        || status == ICameraServiceListener.STATUS_ENUMERATING) continue;
+                cameraIds[idCount] = mDeviceStatus.keyAt(i);
+                idCount++;
+            }
+            return cameraIds;
+        }
+        private static void sortCameraIds(String[] cameraIds) {
             // The sort logic must match the logic in
             // libcameraservice/common/CameraProviderManager.cpp::getAPI1CompatibleCameraDeviceIds
             Arrays.sort(cameraIds, new Comparator<String>() {
@@ -1054,6 +1063,89 @@
                             return s1.compareTo(s2);
                         }
                     }});
+
+        }
+
+        public static boolean cameraStatusesContains(CameraStatus[] cameraStatuses, String id) {
+            for (CameraStatus c : cameraStatuses) {
+                if (c.cameraId.equals(id)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public String[] getCameraIdListNoLazy() {
+            CameraStatus[] cameraStatuses;
+            ICameraServiceListener.Stub testListener = new ICameraServiceListener.Stub() {
+                @Override
+                public void onStatusChanged(int status, String id) throws RemoteException {
+                }
+                @Override
+                public void onTorchStatusChanged(int status, String id) throws RemoteException {
+                }
+                @Override
+                public void onCameraAccessPrioritiesChanged() {
+                }};
+
+            String[] cameraIds = null;
+            synchronized (mLock) {
+                connectCameraServiceLocked();
+                try {
+                    // The purpose of the addListener, removeListener pair here is to get a fresh
+                    // list of camera ids from cameraserver. We do this since for in test processes,
+                    // changes can happen w.r.t non-changeable permissions (eg: SYSTEM_CAMERA
+                    // permissions can be effectively changed by calling
+                    // adopt(drop)ShellPermissionIdentity()).
+                    // Camera devices, which have their discovery affected by these permission
+                    // changes, will not have clients get callbacks informing them about these
+                    // devices going offline (in real world scenarios, these permissions aren't
+                    // changeable). Future calls to getCameraIdList() will reflect the changes in
+                    // the camera id list after getCameraIdListNoLazy() is called.
+                    cameraStatuses = mCameraService.addListener(testListener);
+                    mCameraService.removeListener(testListener);
+                    for (CameraStatus c : cameraStatuses) {
+                        onStatusChangedLocked(c.status, c.cameraId);
+                    }
+                    Set<String> deviceCameraIds = mDeviceStatus.keySet();
+                    ArrayList<String> deviceIdsToRemove = new ArrayList<String>();
+                    for (String deviceCameraId : deviceCameraIds) {
+                        // Its possible that a device id was removed without a callback notifying
+                        // us. This may happen in case a process 'drops' system camera permissions
+                        // (even though the permission isn't a changeable one, tests may call
+                        // adoptShellPermissionIdentity() and then dropShellPermissionIdentity().
+                        if (!cameraStatusesContains(cameraStatuses, deviceCameraId)) {
+                            deviceIdsToRemove.add(deviceCameraId);
+                        }
+                    }
+                    for (String id : deviceIdsToRemove) {
+                        onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, id);
+                    }
+                } catch (ServiceSpecificException e) {
+                    // Unexpected failure
+                    throw new IllegalStateException("Failed to register a camera service listener",
+                            e);
+                } catch (RemoteException e) {
+                    // Camera service is now down, leave mCameraService as null
+                }
+                cameraIds = extractCameraIdListLocked();
+            }
+            sortCameraIds(cameraIds);
+            return cameraIds;
+        }
+
+        /**
+         * Get a list of all camera IDs that are at least PRESENT; ignore devices that are
+         * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
+         */
+        public String[] getCameraIdList() {
+            String[] cameraIds = null;
+            synchronized (mLock) {
+                // Try to make sure we have an up-to-date list of camera devices.
+                connectCameraServiceLocked();
+                cameraIds = extractCameraIdListLocked();
+            }
+            sortCameraIds(cameraIds);
             return cameraIds;
         }
 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6a2fc8a..43c197a 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -880,11 +880,10 @@
          * @throws IllegalArgumentException if the physical camera id is not valid
          *
          * @param key The metadata field to write.
-         * @param value The value to set the field to, which must be of a matching
+         * @param value The value to set the field to, which must be of a matching type to the key.
          * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained
          *                         via calls to {@link CameraCharacteristics#getPhysicalCameraIds}.
          * @return The builder object.
-         * type to the key.
          */
         public <T> Builder setPhysicalCameraKey(@NonNull Key<T> key, T value,
                 @NonNull String physicalCameraId) {
@@ -2790,6 +2789,12 @@
      * output capture result.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
+     * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
+     * sensitivity from last capture result of an auto request for a manual request, in order
+     * to achieve the same brightness in the output image, the application should also
+     * set postRawSensitivityBoost.</p>
      * <p><b>Units</b>: ISO arithmetic units</p>
      * <p><b>Range of valid values:</b><br>
      * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -2800,9 +2805,11 @@
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
      * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ba451e5..1007fd9 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3428,6 +3428,12 @@
      * output capture result.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
+     * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
+     * sensitivity from last capture result of an auto request for a manual request, in order
+     * to achieve the same brightness in the output image, the application should also
+     * set postRawSensitivityBoost.</p>
      * <p><b>Units</b>: ISO arithmetic units</p>
      * <p><b>Range of valid values:</b><br>
      * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -3438,9 +3444,11 @@
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
      * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 68857da9..bcbc337 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1026,34 +1026,35 @@
         // callback is valid
         executor = checkExecutor(executor, callback);
 
-        // Make sure that there all requests have at least 1 surface; all surfaces are non-null;
-        // the surface isn't a physical stream surface for reprocessing request
-        for (CaptureRequest request : requestList) {
-            if (request.getTargets().isEmpty()) {
-                throw new IllegalArgumentException(
-                        "Each request must have at least one Surface target");
-            }
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
 
-            for (Surface surface : request.getTargets()) {
-                if (surface == null) {
-                    throw new IllegalArgumentException("Null Surface targets are not allowed");
+            // Make sure that there all requests have at least 1 surface; all surfaces are non-null;
+            // the surface isn't a physical stream surface for reprocessing request
+            for (CaptureRequest request : requestList) {
+                if (request.getTargets().isEmpty()) {
+                    throw new IllegalArgumentException(
+                            "Each request must have at least one Surface target");
                 }
 
-                for (int i = 0; i < mConfiguredOutputs.size(); i++) {
-                    OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
-                    if (configuration.isForPhysicalCamera()
-                            && configuration.getSurfaces().contains(surface)) {
-                        if (request.isReprocess()) {
-                            throw new IllegalArgumentException(
-                                    "Reprocess request on physical stream is not allowed");
+                for (Surface surface : request.getTargets()) {
+                    if (surface == null) {
+                        throw new IllegalArgumentException("Null Surface targets are not allowed");
+                    }
+
+                    for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                        OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
+                        if (configuration.isForPhysicalCamera()
+                                && configuration.getSurfaces().contains(surface)) {
+                            if (request.isReprocess()) {
+                                throw new IllegalArgumentException(
+                                        "Reprocess request on physical stream is not allowed");
+                            }
                         }
                     }
                 }
             }
-        }
 
-        synchronized(mInterfaceLock) {
-            checkIfCameraClosedOrInError();
             if (repeating) {
                 stopRepeating();
             }
@@ -2349,14 +2350,21 @@
             if (errorCode == ERROR_CAMERA_BUFFER) {
                 // Because 1 stream id could map to multiple surfaces, we need to specify both
                 // streamId and surfaceId.
-                List<Surface> surfaces =
-                        mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurfaces();
-                for (Surface surface : surfaces) {
+                OutputConfiguration config = mConfiguredOutputs.get(
+                        resultExtras.getErrorStreamId());
+                if (config == null) {
+                    Log.v(TAG, String.format(
+                            "Stream %d has been removed. Skipping buffer lost callback",
+                            resultExtras.getErrorStreamId()));
+                    return;
+                }
+                for (Surface surface : config.getSurfaces()) {
                     if (!request.containsTarget(surface)) {
                         continue;
                     }
                     if (DEBUG) {
-                        Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
+                        Log.v(TAG, String.format(
+                                "Lost output buffer reported for frame %d, target %s",
                                 frameNumber, surface));
                     }
                     failureDispatch = new Runnable() {
@@ -2568,4 +2576,21 @@
             Binder.restoreCallingIdentity(ident);
         }
     }
+
+    @Override
+    public void setCameraAudioRestriction(
+            @CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
+            mRemoteDevice.setCameraAudioRestriction(mode);
+        }
+    }
+
+    @Override
+    public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
+            return mRemoteDevice.getGlobalAudioRestriction();
+        }
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index c8ded8d..3660f29 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -258,4 +258,22 @@
         }
     }
 
+    public void setCameraAudioRestriction(int mode) throws CameraAccessException {
+        try {
+            mRemoteDevice.setCameraAudioRestriction(mode);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public int getGlobalAudioRestriction() throws CameraAccessException {
+        try {
+            return mRemoteDevice.getGlobalAudioRestriction();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
 }
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index c28cf8f..5d1435a 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -767,6 +767,28 @@
     }
 
     @Override
+    public void setCameraAudioRestriction(int mode) {
+        if (mLegacyDevice.isClosed()) {
+            String err = "Cannot set camera audio restriction, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
+        }
+
+        mLegacyDevice.setAudioRestriction(mode);
+    }
+
+    @Override
+    public int getGlobalAudioRestriction() {
+        if (mLegacyDevice.isClosed()) {
+            String err = "Cannot set camera audio restriction, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
+        }
+
+        return mLegacyDevice.getAudioRestriction();
+    }
+
+    @Override
     public IBinder asBinder() {
         // This is solely intended to be used for in-process binding.
         return null;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 908ed09..fbc9ac32 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -550,6 +550,14 @@
         return lastFrame;
     }
 
+    public void setAudioRestriction(int mode) {
+        mRequestThreadManager.setAudioRestriction(mode);
+    }
+
+    public int getAudioRestriction() {
+        return mRequestThreadManager.getAudioRestriction();
+    }
+
     /**
      * Return {@code true} if the device has been closed.
      */
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 690df1a..32411fb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -1104,4 +1104,18 @@
         handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
         condition.block();
     }
+
+    public void setAudioRestriction(int mode) {
+        if (mCamera != null) {
+            mCamera.setAudioRestriction(mode);
+        }
+        throw new IllegalStateException("Camera has been released!");
+    }
+
+    public int getAudioRestriction() {
+        if (mCamera != null) {
+            return mCamera.getAudioRestriction();
+        }
+        throw new IllegalStateException("Camera has been released!");
+    }
 }
diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
index b25ef8d..350bc30 100644
--- a/core/java/android/hardware/display/AmbientBrightnessDayStats.java
+++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java
@@ -17,6 +17,7 @@
 package android.hardware.display;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -136,7 +137,7 @@
             };
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -161,6 +162,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder bucketBoundariesString = new StringBuilder();
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 5b63dcf..4c2e297 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -145,6 +145,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("BrightnessConfiguration{[");
@@ -184,7 +185,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == this) {
             return true;
         }
diff --git a/core/java/android/hardware/display/BrightnessCorrection.java b/core/java/android/hardware/display/BrightnessCorrection.java
index b029acc..22df778 100644
--- a/core/java/android/hardware/display/BrightnessCorrection.java
+++ b/core/java/android/hardware/display/BrightnessCorrection.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -98,13 +99,13 @@
      *
      * @return A string representation.
      */
+    @NonNull
     public String toString() {
         return mImplementation.toString();
     }
 
-
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == this) {
             return true;
         }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 21c49ad..0b25dbd 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -829,23 +829,36 @@
     public interface DeviceConfig {
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for refresh rate in the zone defined by thresholds.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_defaultZoneBehavior
+         */
+        String KEY_REFRESH_RATE_IN_ZONE = "refresh_rate_in_zone";
+
+        /**
+         * Key for accessing the display brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
          * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS =
+        String KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS =
                 "peak_refresh_rate_brightness_thresholds";
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for accessing the ambient brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in lux.
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
-         * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+         * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS = "peak_refresh_rate_ambient_thresholds";
+        String KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS =
+                "peak_refresh_rate_ambient_thresholds";
 
         /**
          * Key for default peak refresh rate
@@ -855,5 +868,16 @@
          * @hide
          */
         String KEY_PEAK_REFRESH_RATE_DEFAULT = "peak_refresh_rate_default";
+
+        /**
+         * Key for controlling which packages are explicitly blocked from running at refresh rates
+         * higher than 60hz. An app may be added to this list if they exhibit performance issues at
+         * higher refresh rates.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.array#config_highRefreshRateBlacklist
+         * @hide
+         */
+        String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
     }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 972ae2a..2295d2b 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -734,6 +734,28 @@
             mHotplugEventListeners = new ArrayMap<>();
 
     /**
+     * Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display
+     * status.
+     * @hide
+     */
+    public interface HdmiControlStatusChangeListener {
+        /**
+         * Called when HDMI Control (CEC) is enabled/disabled.
+         *
+         * @param isCecEnabled status of HDMI Control
+         * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+         * @param isCecAvailable status of CEC support of the connected display (the TV).
+         * {@code true} if supported.
+         *
+         * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+         **/
+        void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+    }
+
+    private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
+            mHdmiControlStatusChangeListeners = new ArrayMap<>();
+
+    /**
      * Listener used to get vendor-specific commands.
      */
     public interface VendorCommandListener {
@@ -826,4 +848,73 @@
             }
         };
     }
+
+    /**
+     * Adds a listener to get informed of {@link HdmiControlStatusChange}.
+     *
+     * <p>To stop getting the notification,
+     * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
+     *
+     * @param listener {@link HdmiControlStatusChangeListener} instance
+     * @see HdmiControlManager#removeHdmiControlStatusChangeListener(
+     * HdmiControlStatusChangeListener)
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
+            Log.e(TAG, "listener is already registered");
+            return;
+        }
+        IHdmiControlStatusChangeListener wrappedListener =
+                getHdmiControlStatusChangeListenerWrapper(listener);
+        mHdmiControlStatusChangeListeners.put(listener, wrappedListener);
+        try {
+            mService.addHdmiControlStatusChangeListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a listener to stop getting informed of {@link HdmiControlStatusChange}.
+     *
+     * @param listener {@link HdmiControlStatusChangeListener} instance to be removed
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        IHdmiControlStatusChangeListener wrappedListener =
+                mHdmiControlStatusChangeListeners.remove(listener);
+        if (wrappedListener == null) {
+            Log.e(TAG, "tried to remove not-registered listener");
+            return;
+        }
+        try {
+            mService.removeHdmiControlStatusChangeListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper(
+            final HdmiControlStatusChangeListener listener) {
+        return new IHdmiControlStatusChangeListener.Stub() {
+            @Override
+            public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
+                listener.onStatusChange(isCecEnabled, isCecAvailable);
+            }
+        };
+    }
+
 }
diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
index 1362116..55b0726 100644
--- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java
@@ -16,6 +16,8 @@
 
 package android.hardware.hdmi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -458,6 +460,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
@@ -493,7 +496,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof HdmiDeviceInfo)) {
             return false;
         }
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index f53f458..2623458 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -15,6 +15,8 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -162,6 +164,7 @@
         dest.writeInt(mMhlSupported ? 1 : 0);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer s = new StringBuffer();
@@ -175,7 +178,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (!(o instanceof HdmiPortInfo)) {
             return false;
         }
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 95eaf75..a8fed2b 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -19,6 +19,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlStatusChangeListener;
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -41,6 +42,8 @@
     HdmiDeviceInfo getActiveSource();
     void oneTouchPlay(IHdmiControlCallback callback);
     void queryDisplayStatus(IHdmiControlCallback callback);
+    void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
+    void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener);
     void addHotplugEventListener(IHdmiHotplugEventListener listener);
     void removeHotplugEventListener(IHdmiHotplugEventListener listener);
     void addDeviceEventListener(IHdmiDeviceEventListener listener);
diff --git a/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
new file mode 100644
index 0000000..1407821
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiControlStatusChangeListener.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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 android.hardware.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * the CEC availability change event.
+ *
+ * @hide
+ */
+oneway interface IHdmiControlStatusChangeListener {
+
+    /**
+     * Called when HDMI Control (CEC) is enabled/disabled.
+     *
+     * @param isCecEnabled status of HDMI Control
+     * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+     * @param isCecAvailable status of CEC support of the connected display (the TV).
+     * {@code true} if supported.
+     *
+     * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+     **/
+    void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index b5da381..a11f2e9 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.contexthub.V1_0.ContextHub;
@@ -248,6 +249,7 @@
         return mChrePatchVersion;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "";
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index d1190ab..754327a 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -192,6 +192,7 @@
         return mNanoAppMessage;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String out = "ContextHubIntentEvent[eventType = " + mEventType
diff --git a/core/java/android/hardware/location/ContextHubMessage.java b/core/java/android/hardware/location/ContextHubMessage.java
index 1c98427..6777c53 100644
--- a/core/java/android/hardware/location/ContextHubMessage.java
+++ b/core/java/android/hardware/location/ContextHubMessage.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -127,7 +128,7 @@
         out.writeByteArray(mData);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<ContextHubMessage> CREATOR
+    public static final @NonNull Parcelable.Creator<ContextHubMessage> CREATOR
             = new Parcelable.Creator<ContextHubMessage>() {
         public ContextHubMessage createFromParcel(Parcel in) {
             return new ContextHubMessage(in);
@@ -138,6 +139,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         int length = mData.length;
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index fbbf687..78cca96 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.location.Location;
 import android.os.Parcel;
@@ -72,7 +73,7 @@
         return mLocation;
     }
 
-    public static final @android.annotation.NonNull Creator<GeofenceHardwareMonitorEvent> CREATOR =
+    public static final @NonNull Creator<GeofenceHardwareMonitorEvent> CREATOR =
             new Creator<GeofenceHardwareMonitorEvent>() {
                 @Override
                 public GeofenceHardwareMonitorEvent createFromParcel(Parcel source) {
@@ -108,6 +109,7 @@
         parcel.writeParcelable(mLocation, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format(
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index ecd369a..c033228 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -79,6 +80,7 @@
         return mIsExecutable;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String mask = "";
diff --git a/core/java/android/hardware/location/NanoApp.java b/core/java/android/hardware/location/NanoApp.java
index 3fbb069..6a734f3 100644
--- a/core/java/android/hardware/location/NanoApp.java
+++ b/core/java/android/hardware/location/NanoApp.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -370,6 +371,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "Id : " + mAppId;
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 0700dd1..1d8b69d 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -131,6 +132,7 @@
                 (versionsMatch(mVersionRestrictionMask, mAppVersion, info.getAppVersion()));
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "nanoAppId: 0x" + Long.toHexString(mAppId)
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index a6c754d..ea11756 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -219,6 +219,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         String retVal = "handle : " + mHandle;
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 078532a..bb3e81a 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -136,7 +137,7 @@
         out.writeByteArray(mMessageBody);
     }
 
-    public static final @android.annotation.NonNull Creator<NanoAppMessage> CREATOR =
+    public static final @NonNull Creator<NanoAppMessage> CREATOR =
             new Creator<NanoAppMessage>() {
                 @Override
                 public NanoAppMessage createFromParcel(Parcel in) {
@@ -149,6 +150,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         int length = mMessageBody.length;
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index f4fd1b6..ec318b7 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -377,7 +377,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof Filter)) return false;
             Filter other = (Filter) obj;
@@ -389,6 +389,7 @@
             return true;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "Filter [mIdentifierTypes=" + mIdentifierTypes
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index b321855..d525753 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -485,6 +485,7 @@
         return new ProgramSelector(programType, primary, secondary, null);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
@@ -502,7 +503,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) return true;
         if (!(obj instanceof ProgramSelector)) return false;
         ProgramSelector other = (ProgramSelector) obj;
@@ -611,6 +612,7 @@
             return mValue;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "Identifier(" + mType + ", " + mValue + ")";
@@ -622,7 +624,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof Identifier)) return false;
             Identifier other = (Identifier) obj;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index a7ff644..6ea2ac4 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -485,6 +485,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "ModuleProperties [mId=" + mId
@@ -507,7 +508,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof ModuleProperties)) return false;
             ModuleProperties other = (ModuleProperties) obj;
@@ -660,6 +661,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
@@ -679,7 +681,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!(obj instanceof BandDescriptor))
@@ -788,6 +790,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
@@ -808,7 +811,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -877,6 +880,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
@@ -891,7 +895,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -997,6 +1001,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "BandConfig [ " + mDescriptor.toString() + "]";
@@ -1011,7 +1016,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!(obj instanceof BandConfig))
@@ -1125,6 +1130,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "FmBandConfig [" + super.toString()
@@ -1145,7 +1151,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -1317,6 +1323,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "AmBandConfig [" + super.toString()
@@ -1332,7 +1339,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (!super.equals(obj))
@@ -1656,6 +1663,7 @@
             return 0;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "ProgramInfo"
@@ -1676,7 +1684,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) return true;
             if (!(obj instanceof ProgramInfo)) return false;
             ProgramInfo other = (ProgramInfo) obj;
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index 76304bd..a882c2f 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -16,6 +16,7 @@
 package android.hardware.radio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -279,7 +280,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) return true;
         if (!(obj instanceof RadioMetadata)) return false;
         Bundle otherBundle = ((RadioMetadata) obj).mBundle;
@@ -308,6 +309,7 @@
         mBundle = in.readBundle();
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("RadioMetadata[");
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 5b5bd76..b1134e1 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,6 +22,7 @@
 import static android.system.OsConstants.EPERM;
 import static android.system.OsConstants.EPIPE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -70,28 +71,27 @@
      * ID used to target any API call to this paricular module. Module
      * properties are returned by listModules() method.
      *
-     * @hide
      ****************************************************************************/
-    public static class ModuleProperties implements Parcelable {
+    public static final class ModuleProperties implements Parcelable {
         /** Unique module ID provided by the native service */
-        @UnsupportedAppUsage
         public final int id;
 
         /** human readable voice detection engine implementor */
+        @NonNull
         public final String implementor;
 
         /** human readable voice detection engine description */
+        @NonNull
         public final String description;
 
         /** Unique voice engine Id (changes with each version) */
-        @UnsupportedAppUsage
+        @NonNull
         public final UUID uuid;
 
         /** Voice detection engine version */
         public final int version;
 
         /** Maximum number of active sound models */
-        @UnsupportedAppUsage
         public final int maxSoundModels;
 
         /** Maximum number of key phrases */
@@ -119,7 +119,6 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
-        @UnsupportedAppUsage
         ModuleProperties(int id, String implementor, String description,
                 String uuid, int version, int maxSoundModels, int maxKeyphrases,
                 int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
@@ -829,7 +828,7 @@
         }
 
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj)
                 return true;
             if (obj == null)
@@ -869,6 +868,7 @@
             return true;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "RecognitionEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
diff --git a/core/java/android/hardware/usb/AccessoryFilter.java b/core/java/android/hardware/usb/AccessoryFilter.java
index 00070fe..f22dad4 100644
--- a/core/java/android/hardware/usb/AccessoryFilter.java
+++ b/core/java/android/hardware/usb/AccessoryFilter.java
@@ -56,6 +56,12 @@
         mVersion = accessory.getVersion();
     }
 
+    public AccessoryFilter(@NonNull AccessoryFilter filter) {
+        mManufacturer = filter.mManufacturer;
+        mModel = filter.mModel;
+        mVersion = filter.mVersion;
+    }
+
     public static AccessoryFilter read(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         String manufacturer = null;
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 6f1aff7..da979c0c 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -80,6 +80,17 @@
         mSerialNumber = device.getSerialNumber();
     }
 
+    public DeviceFilter(@NonNull DeviceFilter filter) {
+        mVendorId = filter.mVendorId;
+        mProductId = filter.mProductId;
+        mClass = filter.mClass;
+        mSubclass = filter.mSubclass;
+        mProtocol = filter.mProtocol;
+        mManufacturerName = filter.mManufacturerName;
+        mProductName = filter.mProductName;
+        mSerialNumber = filter.mSerialNumber;
+    }
+
     public static DeviceFilter read(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         int vendorId = -1;
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 299a00a..c8dbd16 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -24,6 +24,7 @@
 import android.hardware.usb.UsbPortStatus;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 
 /** @hide */
 interface IUsbManager
@@ -54,6 +55,26 @@
      */
     void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId);
 
+    /* Adds packages to the set of "denied and don't ask again" launch preferences for a device */
+    void addDevicePackagesToPreferenceDenied(in UsbDevice device, in String[] packageNames, in UserHandle user);
+
+    /* Adds packages to the set of "denied and don't ask again" launch preferences for an accessory */
+    void addAccessoryPackagesToPreferenceDenied(in UsbAccessory accessory, in String[] packageNames, in UserHandle user);
+
+    /* Removes packages from the set of "denied and don't ask again" launch preferences for a device */
+    void removeDevicePackagesFromPreferenceDenied(in UsbDevice device, in String[] packageNames, in UserHandle user);
+
+    /* Removes packages from the set of "denied and don't ask again" launch preferences for an accessory */
+    void removeAccessoryPackagesFromPreferenceDenied(in UsbAccessory device, in String[] packageNames, in UserHandle user);
+
+    /* Sets the persistent permission granted state for USB device
+     */
+    void setDevicePersistentPermission(in UsbDevice device, int uid, in UserHandle user, boolean shouldBeGranted);
+
+    /* Sets the persistent permission granted state for USB accessory
+     */
+    void setAccessoryPersistentPermission(in UsbAccessory accessory, int uid, in UserHandle user, boolean shouldBeGranted);
+
     /* Returns true if the caller has permission to access the device. */
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index c674480..506230e 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -327,7 +327,7 @@
         return false;
     }
 
-
+    @NonNull
     @Override
     public String toString() {
         return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes)
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 5e9a410..43c418e 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -17,6 +17,7 @@
 package android.hardware.usb;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.usb.V1_0.Constants;
 import android.os.Parcel;
@@ -322,6 +323,7 @@
         return mContaminantProtectionStatus;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UsbPortStatus{connected=" + isConnected()
@@ -352,7 +354,7 @@
         dest.writeInt(mContaminantDetectionStatus);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
+    public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR =
             new Parcelable.Creator<UsbPortStatus>() {
         @Override
         public UsbPortStatus createFromParcel(Parcel in) {
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 10667ae..106b7be 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -22,5 +22,5 @@
     void onMeteredIfacesChanged(in String[] meteredIfaces);
     void onRestrictBackgroundChanged(boolean restrictBackground);
     void onUidPoliciesChanged(int uid, int uidPolicies);
-    void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
+    void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask);
 }
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..90327663 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,7 +76,7 @@
     SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
     void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
     String getSubscriptionPlansOwner(int subId);
-    void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
+    void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask, long timeoutMillis, String callingPackage);
 
     void factoryReset(String subscriber);
 
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 83813da..45d0c73 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -861,6 +861,7 @@
             return mResourceId;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return new StringBuilder()
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index fe7632c..b066a15 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -157,40 +157,6 @@
                 write_native(b, myFd);
             }
         }
-
-        /**
-         * Wait until the data in sending queue is emptied. A polling version
-         * for flush implementation.
-         * @throws IOException
-         *             if an i/o error occurs.
-         */
-        @Override
-        public void flush() throws IOException {
-            FileDescriptor myFd = fd;
-            if (myFd == null) throw new IOException("socket closed");
-
-            // Loop until the output buffer is empty.
-            Int32Ref pending = new Int32Ref(0);
-            while (true) {
-                try {
-                    // See linux/net/unix/af_unix.c
-                    Os.ioctlInt(myFd, OsConstants.TIOCOUTQ, pending);
-                } catch (ErrnoException e) {
-                    throw e.rethrowAsIOException();
-                }
-
-                if (pending.value <= 0) {
-                    // The output buffer is empty.
-                    break;
-                }
-
-                try {
-                    Thread.sleep(10);
-                } catch (InterruptedException ie) {
-                    break;
-                }
-            }
-        }
     }
 
     private native int read_native(FileDescriptor fd) throws IOException;
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index a809b28..2cf2a65 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -85,6 +85,9 @@
     private static final long OUI_MASK = MacAddress.fromString("ff:ff:ff:0:0:0").mAddr;
     private static final long NIC_MASK = MacAddress.fromString("0:0:0:ff:ff:ff").mAddr;
     private static final MacAddress BASE_GOOGLE_MAC = MacAddress.fromString("da:a1:19:0:0:0");
+    /** Default wifi MAC address used for a special purpose **/
+    private static final MacAddress DEFAULT_MAC_ADDRESS =
+            MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
 
     // Internal representation of the MAC address as a single 8 byte long.
     // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
@@ -361,16 +364,7 @@
      * @hide
      */
     public static @NonNull MacAddress createRandomUnicastAddress() {
-        SecureRandom r = new SecureRandom();
-        long addr = r.nextLong() & VALID_LONG_MASK;
-        addr |= LOCALLY_ASSIGNED_MASK;
-        addr &= ~MULTICAST_MASK;
-        MacAddress mac = new MacAddress(addr);
-        // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here.
-        if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
-            return createRandomUnicastAddress();
-        }
-        return mac;
+        return createRandomUnicastAddress(null, new SecureRandom());
     }
 
     /**
@@ -380,18 +374,23 @@
      * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
      *
      * @param base a base MacAddress whose OUI is used for generating the random address.
+     *             If base == null then the OUI will also be randomized.
      * @param r a standard Java Random object used for generating the random address.
      * @return a random locally assigned MacAddress.
      *
      * @hide
      */
     public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
-        long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
+        long addr;
+        if (base == null) {
+            addr = r.nextLong() & VALID_LONG_MASK;
+        } else {
+            addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
+        }
         addr |= LOCALLY_ASSIGNED_MASK;
         addr &= ~MULTICAST_MASK;
         MacAddress mac = new MacAddress(addr);
-        // WifiInfo.DEFAULT_MAC_ADDRESS is being used for another purpose, so do not use it here.
-        if (mac.toString().equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
+        if (mac.equals(DEFAULT_MAC_ADDRESS)) {
             return createRandomUnicastAddress(base, r);
         }
         return mac;
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 0a9a3c8..a101da7 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.ScanResult;
@@ -152,7 +153,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -166,6 +167,7 @@
         return Objects.hash(type, wifiKey);
     }
 
+    @NonNull
     @Override
     public String toString() {
         switch (type) {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index bf27262..628dcd2 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -380,6 +380,7 @@
         @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
         @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
         @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
-        @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) { }
+        @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
+                long networkTypeMask) { }
     }
 }
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index a173b0c..668e966 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -170,7 +172,7 @@
      * not considered equal to each other.
      */
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -187,6 +189,7 @@
         return Objects.hash(start, bucketWidth, activeNetworkRssiBoost) ^ Arrays.hashCode(rssiBuckets);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index effc1aa..64b3bf1 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
@@ -182,7 +183,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -220,6 +221,7 @@
         return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder out = new StringBuilder(
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index d6deba5..5bc9953 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -236,6 +236,7 @@
         return lp;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer str = new StringBuffer();
@@ -267,7 +268,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) return true;
 
         if (!(obj instanceof StaticIpConfiguration)) return false;
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index fbc281f..994c794 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -18,6 +18,7 @@
 
 import static android.util.Patterns.GOOD_IRI_CHAR;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -132,6 +133,7 @@
         if (mScheme.equals("")) mScheme = "http";
     }
 
+    @NonNull
     @Override
     public String toString() {
         String port = "";
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
index e3a93a8..bc9d8c5 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -91,7 +93,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -105,6 +107,7 @@
         return Objects.hash(ssid, bssid);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index 4dd2ace..b1de74e 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -17,6 +17,7 @@
 package android.net.apf;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.res.Resources;
@@ -91,6 +92,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
@@ -98,7 +100,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (!(obj instanceof  ApfCapabilities)) return false;
         final ApfCapabilities other = (ApfCapabilities) obj;
         return apfVersionSupported == other.apfVersionSupported
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index e9c209c..8243be9 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -185,6 +186,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever";
@@ -193,7 +195,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
         final ApfProgramEvent other = (ApfProgramEvent) obj;
         return lifetime == other.lifetime
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index b963777..eac5579 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -260,6 +261,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("ApfStats(")
@@ -276,7 +278,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
         final ApfStats other = (ApfStats) obj;
         return durationMs == other.durationMs
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 2fed736..5f9f507 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -97,13 +98,14 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
         final DhcpClientEvent other = (DhcpClientEvent) obj;
         return TextUtils.equals(msg, other.msg)
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index 8760004..32efb5a 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -16,6 +16,7 @@
 
 package android.net.metrics;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -108,6 +109,7 @@
         return (0xFFFF0000 & errorCode) | (0xFF & option);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index ba05c59..f14abb8 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -95,6 +97,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("IpManagerEvent(%s, %dms)",
@@ -102,7 +105,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
         final IpManagerEvent other = (IpManagerEvent) obj;
         return eventType == other.eventType
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d4ba294..79e01d7 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -16,6 +16,8 @@
 
 package android.net.metrics;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -85,6 +87,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         int hi = eventType & 0xff00;
@@ -94,7 +97,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
         final IpReachabilityEvent other = (IpReachabilityEvent) obj;
         return eventType == other.eventType;
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 0c57ec6..fe603cf 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -17,6 +17,8 @@
 package android.net.metrics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -115,6 +117,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("NetworkEvent(%s, %dms)",
@@ -122,7 +125,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
         final NetworkEvent other = (NetworkEvent) obj;
         return eventType == other.eventType
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index 3fd87c2..661f648 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -17,6 +17,7 @@
 package android.net.metrics;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -85,6 +86,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("RaEvent(lifetimes: ")
@@ -98,7 +100,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
         final RaEvent other = (RaEvent) obj;
         return routerLifetime == other.routerLifetime
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 1aaa50d..8fab64a 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -164,6 +165,7 @@
         return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
@@ -171,7 +173,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
         final ValidationProbeEvent other = (ValidationProbeEvent) obj;
         return durationMs == other.durationMs
diff --git a/core/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java
index f7e494d..4e88149 100644
--- a/core/java/android/net/util/MultinetworkPolicyTracker.java
+++ b/core/java/android/net/util/MultinetworkPolicyTracker.java
@@ -64,7 +64,7 @@
 
     private final Context mContext;
     private final Handler mHandler;
-    private final Runnable mReevaluateRunnable;
+    private final Runnable mAvoidBadWifiCallback;
     private final List<Uri> mSettingsUris;
     private final ContentResolver mResolver;
     private final SettingObserver mSettingObserver;
@@ -81,12 +81,7 @@
     public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
         mContext = ctx;
         mHandler = handler;
-        mReevaluateRunnable = () -> {
-            if (updateAvoidBadWifi() && avoidBadWifiCallback != null) {
-                avoidBadWifiCallback.run();
-            }
-            updateMeteredMultipathPreference();
-        };
+        mAvoidBadWifiCallback = avoidBadWifiCallback;
         mSettingsUris = Arrays.asList(
             Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
             Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
@@ -95,15 +90,15 @@
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                reevaluate();
+                reevaluateInternal();
             }
         };
 
-        TelephonyManager.from(ctx).listen(new PhoneStateListener() {
+        TelephonyManager.from(ctx).listen(new PhoneStateListener(handler.getLooper()) {
             @Override
             public void onActiveDataSubscriptionIdChanged(int subId) {
                 mActiveSubId = subId;
-                reevaluate();
+                reevaluateInternal();
             }
         }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
 
@@ -119,7 +114,7 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         mContext.registerReceiverAsUser(
-                mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null);
+                mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler);
 
         reevaluate();
     }
@@ -164,7 +159,17 @@
 
     @VisibleForTesting
     public void reevaluate() {
-        mHandler.post(mReevaluateRunnable);
+        mHandler.post(this::reevaluateInternal);
+    }
+
+    /**
+     * Reevaluate the settings. Must be called on the handler thread.
+     */
+    private void reevaluateInternal() {
+        if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
+            mAvoidBadWifiCallback.run();
+        }
+        updateMeteredMultipathPreference();
     }
 
     public boolean updateAvoidBadWifi() {
diff --git a/core/java/android/os/BatterySaverPolicyConfig.java b/core/java/android/os/BatterySaverPolicyConfig.java
index 3801cbd..3f6ce4f 100644
--- a/core/java/android/os/BatterySaverPolicyConfig.java
+++ b/core/java/android/os/BatterySaverPolicyConfig.java
@@ -161,6 +161,7 @@
         dest.writeInt(mLocationMode);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 5533721..2c9333b 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -1029,7 +1030,17 @@
                 Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
                         + (transactionName != null ? transactionName : code));
             }
-            res = onTransact(code, data, reply, flags);
+
+            if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+                AppOpsManager.startNotedAppOpsCollection(callingUid);
+                try {
+                    res = onTransact(code, data, reply, flags);
+                } finally {
+                    AppOpsManager.finishNotedAppOpsCollection();
+                }
+            } else {
+                res = onTransact(code, data, reply, flags);
+            }
         } catch (RemoteException|RuntimeException e) {
             if (observer != null) {
                 observer.callThrewException(callSession, e);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c74cef8..b3b4f78 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.util.Log;
 import android.util.SparseIntArray;
 
@@ -506,9 +507,18 @@
             }
         }
 
+        final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
+                AppOpsManager.pauseNotedAppOpsCollection();
+
+        if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isCollectingNotedAppOps()) {
+            flags |= FLAG_COLLECT_NOTED_APP_OPS;
+        }
+
         try {
             return transactNative(code, data, reply, flags);
         } finally {
+            AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
+
             if (transactListener != null) {
                 transactListener.onTransactEnded(session);
             }
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 83f88ad..12bce8a 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -170,6 +170,11 @@
     int FLAG_ONEWAY             = 0x00000001;
 
     /**
+     * @hide
+     */
+    int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
+
+    /**
      * Limit that should be placed on IPC sizes to keep them safely under the
      * transaction buffer limit.
      * @hide
diff --git a/core/java/android/os/IMaintenanceActivityListener.aidl b/core/java/android/os/IMaintenanceActivityListener.aidl
deleted file mode 100644
index 6a2581f..0000000
--- a/core/java/android/os/IMaintenanceActivityListener.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/** @hide */
-oneway interface IMaintenanceActivityListener {
-    void onMaintenanceActivityChanged(boolean active);
-}
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index a94fd65..09e1c0f 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -255,7 +255,7 @@
          * @inheritDoc
          */
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (this == obj) {
                 return true;
             }
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
index a1f2430..7e858e1 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -117,6 +118,7 @@
     /**
      * Print this report as a string.
      */
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("Incident(");
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 7782753..0de09ef 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -21,9 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.UnsupportedAppUsage;
-import android.content.LocaleProto;
 import android.icu.util.ULocale;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -143,26 +141,6 @@
     }
 
     /**
-     * Helper to write LocaleList to a protocol buffer output stream.  Assumes the parent
-     * protobuf has declared the locale as repeated.
-     *
-     * @param protoOutputStream Stream to write the locale to.
-     * @param fieldId Field Id of the Locale as defined in the parent message.
-     * @hide
-     */
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        for (int i = 0; i < mList.length; i++) {
-            final Locale locale = mList[i];
-            final long token = protoOutputStream.start(fieldId);
-            protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage());
-            protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry());
-            protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant());
-            protoOutputStream.write(LocaleProto.SCRIPT, locale.getScript());
-            protoOutputStream.end(token);
-        }
-    }
-
-    /**
      * Retrieves a String representation of the language tags in this list.
      */
     @NonNull
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e1b5542..50487e9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -267,7 +268,9 @@
     private static final int EX_UNSUPPORTED_OPERATION = -7;
     private static final int EX_SERVICE_SPECIFIC = -8;
     private static final int EX_PARCELABLE = -9;
-    private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
+    /** @hide */
+    public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
+    private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128;  // special; see below
     // EX_TRANSACTION_FAILED is used exclusively in native code.
     // see libbinder's binder/Status.h
     private static final int EX_TRANSACTION_FAILED = -129;
@@ -1866,6 +1869,8 @@
      * @see #readException
      */
     public final void writeException(@NonNull Exception e) {
+        AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
         int code = 0;
         if (e instanceof Parcelable
                 && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1944,6 +1949,8 @@
      * @see #readException
      */
     public final void writeNoException() {
+        AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
         // Despite the name of this function ("write no exception"),
         // it should instead be thought of as "write the RPC response
         // header", but because this function name is written out by
@@ -1951,14 +1958,14 @@
         //
         // The response header, in the non-exception case (see also
         // writeException above, also called by the AIDL compiler), is
-        // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+        // either a 0 (the default case), or EX_HAS_STRICTMODE_REPLY_HEADER if
         // StrictMode has gathered up violations that have occurred
         // during a Binder call, in which case we write out the number
         // of violations and their details, serialized, before the
         // actual RPC respons data.  The receiving end of this is
         // readException(), below.
         if (StrictMode.hasGatheredViolations()) {
-            writeInt(EX_HAS_REPLY_HEADER);
+            writeInt(EX_HAS_STRICTMODE_REPLY_HEADER);
             final int sizePosition = dataPosition();
             writeInt(0);  // total size of fat header, to be filled in later
             StrictMode.writeGatheredViolationsToParcel(this);
@@ -2005,7 +2012,13 @@
     @TestApi
     public final int readExceptionCode() {
         int code = readInt();
-        if (code == EX_HAS_REPLY_HEADER) {
+        if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+            AppOpsManager.readAndLogNotedAppops(this);
+            // Read next header or real exception if there is no more header
+            code = readInt();
+        }
+
+        if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
             int headerSize = readInt();
             if (headerSize == 0) {
                 Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 2a4576a..bcb94ce 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -79,6 +79,7 @@
     /**
      * Wrapped {@link ParcelFileDescriptor}, if any. Used to avoid
      * double-closing {@link #mFd}.
+     * mClosed is always true if mWrapped is non-null.
      */
     private final ParcelFileDescriptor mWrapped;
 
@@ -616,10 +617,11 @@
     public static File getFile(FileDescriptor fd) throws IOException {
         try {
             final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
-            if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+            if (OsConstants.S_ISREG(Os.stat(path).st_mode)
+                    || OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
                 return new File(path);
             } else {
-                throw new IOException("Not a regular file: " + path);
+                throw new IOException("Not a regular file or character device: " + path);
             }
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
@@ -1022,6 +1024,7 @@
         }
         try {
             if (!mClosed) {
+                // mWrapped was and is null.
                 closeWithStatus(Status.LEAKED, null);
             }
         } finally {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 30e5959..76e728a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -182,6 +182,12 @@
      */
     public static final int NETWORK_STACK_UID = 1073;
 
+    /**
+     * Defines the UID/GID for fs-verity certificate ownership in keystore.
+     * @hide
+     */
+    public static final int FSVERITY_CERT_UID = 1075;
+
     /** {@hide} */
     public static final int NOBODY_UID = 9999;
 
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7991cd4..f641731 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -76,6 +76,16 @@
         return mServiceManager.listServices(dumpPriority);
     }
 
+    public void registerForNotifications(String name, IServiceCallback cb)
+            throws RemoteException {
+        throw new RemoteException();
+    }
+
+    public void unregisterForNotifications(String name, IServiceCallback cb)
+            throws RemoteException {
+        throw new RemoteException();
+    }
+
     /**
      * Same as mServiceManager but used by apps.
      *
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 03d5d3e..49ce40b 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 
@@ -44,6 +45,7 @@
         this.errorCode = errorCode;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + " (code " + errorCode + ")";
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index cdae72e..d4abf28 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -39,6 +39,13 @@
  * Gives access to the system properties store.  The system properties
  * store contains a list of string key-value pairs.
  *
+ * <p>Use this class only for the system properties that are local. e.g., within
+ * an app, a partition, or a module. For system properties used across the
+ * boundaries, formally define them in <code>*.sysprop</code> files and use the
+ * auto-generated methods. For more information, see <a href=
+ * "https://source.android.com/devices/architecture/sysprops-apis">Implementing
+ * System Properties as APIs</a>.</p>
+ *
  * {@hide}
  */
 @SystemApi
@@ -189,6 +196,8 @@
      * Set the value for the given {@code key} to {@code val}.
      *
      * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
+     * @throws RuntimeException if the property cannot be set, for example, if it was blocked by
+     * SELinux. libc will log the underlying reason.
      * @hide
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 29af17a..dd5e20e 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.IUpdateEngine;
 import android.os.IUpdateEngineCallback;
@@ -320,8 +321,8 @@
      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
      * {@code headerKeyValuePairs} parameters.
      */
-    public void applyPayload(FileDescriptor fd, long offset, long size,
-            String[] headerKeyValuePairs) {
+    public void applyPayload(@NonNull FileDescriptor fd, long offset, long size,
+            @NonNull String[] headerKeyValuePairs) {
         try {
             mUpdateEngine.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
         } catch (RemoteException e) {
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 4e17f7e..0754dc7 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -128,8 +128,9 @@
     @UnsupportedAppUsage
     public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
 
+    /** The userId represented by this UserHandle. */
     @UnsupportedAppUsage
-    final int mHandle;
+    final @UserIdInt int mHandle;
 
     /**
      * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
@@ -270,7 +271,7 @@
     }
 
     /** @hide */
-    public static int getSharedAppGid(int userId, int appId) {
+    public static int getSharedAppGid(@UserIdInt int userId, @AppIdInt int appId) {
         if (appId >= AID_APP_START && appId <= AID_APP_END) {
             return (appId - AID_APP_START) + AID_SHARED_GID_START;
         } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
@@ -300,7 +301,7 @@
     }
 
     /** @hide */
-    public static int getCacheAppGid(int userId, int appId) {
+    public static int getCacheAppGid(@UserIdInt int userId, @AppIdInt int appId) {
         if (appId >= AID_APP_START && appId <= AID_APP_END) {
             return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
         } else {
@@ -432,8 +433,8 @@
 
     /** @hide */
     @UnsupportedAppUsage
-    public UserHandle(int h) {
-        mHandle = h;
+    public UserHandle(@UserIdInt int userId) {
+        mHandle = userId;
     }
 
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a7fa96b..af574da 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1404,7 +1404,7 @@
 
     /**
      * Used to check if this process is running under the primary user. The primary user
-     * is the first human user on a device.
+     * is the first human user on a device. This is not supported in headless system user mode.
      *
      * @return whether this process is running under the primary user.
      * @hide
@@ -1794,14 +1794,14 @@
     /**
      * Returns the UserInfo object describing a specific user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle the user handle of the user whose information is being requested.
+     * @param userId the user handle of the user whose information is being requested.
      * @return the UserInfo object for a specific user.
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo getUserInfo(@UserIdInt int userHandle) {
+    public UserInfo getUserInfo(@UserIdInt int userId) {
         try {
-            return mService.getUserInfo(userHandle);
+            return mService.getUserInfo(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2066,15 +2066,15 @@
      *
      * @param name the user's name
      * @param flags flags that identify the type of user and other properties.
-     * @param userHandle new user will be a profile of this user.
+     * @param userId new user will be a profile of this user.
      *
      * @return the {@link UserInfo} object for the created user, or null if the user
      *         could not be created.
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) {
-        return createProfileForUser(name, flags, userHandle, null);
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId) {
+        return createProfileForUser(name, flags, userId, null);
     }
 
     /**
@@ -2084,17 +2084,17 @@
      *
      * @param name the user's name
      * @param flags flags that identify the type of user and other properties.
-     * @param userHandle new user will be a profile of this user.
+     * @param userId new user will be a profile of this user.
      * @param disallowedPackages packages that will not be installed in the profile being created.
      *
      * @return the {@link UserInfo} object for the created user, or null if the user
      *         could not be created.
      * @hide
      */
-    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle,
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId,
             String[] disallowedPackages) {
         try {
-            return mService.createProfileForUser(name, flags, userHandle, disallowedPackages);
+            return mService.createProfileForUser(name, flags, userId, disallowedPackages);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2109,9 +2109,9 @@
      * @hide
      */
     public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
-            @UserIdInt int userHandle, String[] disallowedPackages) {
+            @UserIdInt int userId, String[] disallowedPackages) {
         try {
-            return mService.createProfileForUserEvenWhenDisallowed(name, flags, userHandle,
+            return mService.createProfileForUserEvenWhenDisallowed(name, flags, userId,
                     disallowedPackages);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
@@ -2280,12 +2280,12 @@
      * @hide
      * Marks the guest user for deletion to allow a new guest to be created before deleting
      * the current user who is a guest.
-     * @param userHandle
+     * @param userId
      * @return
      */
-    public boolean markGuestForDeletion(@UserIdInt int userHandle) {
+    public boolean markGuestForDeletion(@UserIdInt int userId) {
         try {
-            return mService.markGuestForDeletion(userHandle);
+            return mService.markGuestForDeletion(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2317,16 +2317,16 @@
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
      *
-     * @param userHandle the id of the user to become admin
+     * @param userId the id of the user to become admin
      * @hide
      */
     @RequiresPermission(allOf = {
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public void setUserAdmin(@UserIdInt int userHandle) {
+    public void setUserAdmin(@UserIdInt int userId) {
         try {
-            mService.setUserAdmin(userHandle);
+            mService.setUserAdmin(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2337,9 +2337,9 @@
      *
      * @hide
      */
-    public void evictCredentialEncryptionKey(@UserIdInt int userHandle) {
+    public void evictCredentialEncryptionKey(@UserIdInt int userId) {
         try {
-            mService.evictCredentialEncryptionKey(userHandle);
+            mService.evictCredentialEncryptionKey(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2400,9 +2400,9 @@
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public @Nullable String getUserAccount(@UserIdInt int userHandle) {
+    public @Nullable String getUserAccount(@UserIdInt int userId) {
         try {
-            return mService.getUserAccount(userHandle);
+            return mService.getUserAccount(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2416,9 +2416,9 @@
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public void setUserAccount(@UserIdInt int userHandle, @Nullable String accountName) {
+    public void setUserAccount(@UserIdInt int userId, @Nullable String accountName) {
         try {
-            mService.setUserAccount(userHandle, accountName);
+            mService.setUserAccount(userId, accountName);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2477,20 +2477,19 @@
     }
 
     /**
-     * Returns list of the profiles of userHandle including
-     * userHandle itself.
+     * Returns list of the profiles of userId including userId itself.
      * Note that this returns both enabled and not enabled profiles. See
      * {@link #getEnabledProfiles(int)} if you need only the enabled ones.
      *
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle profiles of this user will be returned.
+     * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
-    public List<UserInfo> getProfiles(@UserIdInt int userHandle) {
+    public List<UserInfo> getProfiles(@UserIdInt int userId) {
         try {
-            return mService.getProfiles(userHandle, false /* enabledOnly */);
+            return mService.getProfiles(userId, false /* enabledOnly */);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2512,19 +2511,18 @@
     }
 
     /**
-     * Returns list of the profiles of userHandle including
-     * userHandle itself.
+     * Returns list of the profiles of userId including userId itself.
      * Note that this returns only enabled.
      *
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle profiles of this user will be returned.
+     * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
-    public List<UserInfo> getEnabledProfiles(@UserIdInt int userHandle) {
+    public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) {
         try {
-            return mService.getProfiles(userHandle, true /* enabledOnly */);
+            return mService.getProfiles(userId, true /* enabledOnly */);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2584,14 +2582,14 @@
 
     /**
      * Returns the device credential owner id of the profile from
-     * which this method is called, or userHandle if called from a user that
+     * which this method is called, or userId if called from a user that
      * is not a profile.
      *
      * @hide
      */
-    public int getCredentialOwnerProfile(@UserIdInt int userHandle) {
+    public int getCredentialOwnerProfile(@UserIdInt int userId) {
         try {
-            return mService.getCredentialOwnerProfile(userHandle);
+            return mService.getCredentialOwnerProfile(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2604,9 +2602,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo getProfileParent(@UserIdInt int userHandle) {
+    public UserInfo getProfileParent(@UserIdInt int userId) {
         try {
-            return mService.getProfileParent(userHandle);
+            return mService.getProfileParent(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2785,13 +2783,13 @@
     /**
      * Removes a user and all associated data.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle the integer handle of the user, where 0 is the primary user.
+     * @param userId the integer handle of the user.
      * @hide
      */
     @UnsupportedAppUsage
-    public boolean removeUser(@UserIdInt int userHandle) {
+    public boolean removeUser(@UserIdInt int userId) {
         try {
-            return mService.removeUser(userHandle);
+            return mService.removeUser(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2823,9 +2821,9 @@
      * @see {@link #removeUser(int)}
      * @hide
      */
-    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         try {
-            return mService.removeUserEvenWhenDisallowed(userHandle);
+            return mService.removeUserEvenWhenDisallowed(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2835,13 +2833,13 @@
      * Updates the user's name.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
-     * @param userHandle the user's integer handle
+     * @param userId the user's integer id
      * @param name the new name for the user
      * @hide
      */
-    public void setUserName(@UserIdInt int userHandle, String name) {
+    public void setUserName(@UserIdInt int userId, String name) {
         try {
-            mService.setUserName(userHandle, name);
+            mService.setUserName(userId, name);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2862,13 +2860,13 @@
 
     /**
      * Sets the user's photo.
-     * @param userHandle the user for whom to change the photo.
+     * @param userId the user for whom to change the photo.
      * @param icon the bitmap to set as the photo.
      * @hide
      */
-    public void setUserIcon(@UserIdInt int userHandle, Bitmap icon) {
+    public void setUserIcon(@UserIdInt int userId, Bitmap icon) {
         try {
-            mService.setUserIcon(userHandle, icon);
+            mService.setUserIcon(userId, icon);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2889,15 +2887,15 @@
 
     /**
      * Returns a file descriptor for the user's photo. PNG data can be read from this file.
-     * @param userHandle the user whose photo we want to read.
+     * @param userId the user whose photo we want to read.
      * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
      * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
      * @hide
      */
     @UnsupportedAppUsage
-    public Bitmap getUserIcon(@UserIdInt int userHandle) {
+    public Bitmap getUserIcon(@UserIdInt int userId) {
         try {
-            ParcelFileDescriptor fd = mService.getUserIcon(userHandle);
+            ParcelFileDescriptor fd = mService.getUserIcon(userId);
             if (fd != null) {
                 try {
                     return BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
@@ -2999,27 +2997,27 @@
     }
 
     /**
-     * Returns a serial number on this device for a given userHandle. User handles can be recycled
+     * Returns a serial number on this device for a given userId. User handles can be recycled
      * when deleting and creating users, but serial numbers are not reused until the device is wiped.
-     * @param userHandle
-     * @return a serial number associated with that user, or -1 if the userHandle is not valid.
+     * @param userId
+     * @return a serial number associated with that user, or -1 if the userId is not valid.
      * @hide
      */
     @UnsupportedAppUsage
-    public int getUserSerialNumber(@UserIdInt int userHandle) {
+    public int getUserSerialNumber(@UserIdInt int userId) {
         try {
-            return mService.getUserSerialNumber(userHandle);
+            return mService.getUserSerialNumber(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Returns a userHandle on this device for a given user serial number. User handles can be
+     * Returns a userId on this device for a given user serial number. User handles can be
      * recycled when deleting and creating users, but serial numbers are not reused until the device
      * is wiped.
      * @param userSerialNumber
-     * @return the userHandle associated with that user serial number, or -1 if the serial number
+     * @return the userId associated with that user serial number, or -1 if the serial number
      * is not valid.
      * @hide
      */
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index f302263..a5b71f6 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,7 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.Context;
@@ -136,7 +137,7 @@
             String[] disallowedPackages);
 
     /**
-     * Same as {@link UserManager#removeUser(int userHandle)}, but bypasses the check for
+     * Same as {@link UserManager#removeUser(int userId)}, but bypasses the check for
      * {@link UserManager#DISALLOW_REMOVE_USER} and
      * {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE} and does not require the
      * {@link android.Manifest.permission#MANAGE_USERS} permission.
@@ -231,4 +232,9 @@
      * found.
      */
     public abstract @Nullable UserInfo getUserInfo(@UserIdInt int userId);
+
+    /**
+     * Gets all {@link UserInfo UserInfos}.
+     */
+    public abstract @NonNull UserInfo[] getUserInfos();
 }
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 702b41b..26da0a0 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -53,7 +53,7 @@
     public static final int MAX_AMPLITUDE = 255;
 
     /**
-     * A click effect.
+     * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
      *
      * @see #get(int)
      */
@@ -67,7 +67,7 @@
     public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
 
     /**
-     * A tick effect.
+     * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
      * @see #get(int)
      */
     public static final int EFFECT_TICK = Effect.TICK;
@@ -89,7 +89,7 @@
     public static final int EFFECT_POP = Effect.POP;
 
     /**
-     * A heavy click effect.
+     * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
      * @see #get(int)
      */
     public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 23c54f4..1c78b08 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -72,6 +72,8 @@
      * ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0",
      *  "android.hardware.camera.device@3.2"]. There are no duplicates.
      *
+     * For AIDL HALs, the version is stripped away
+     * (e.g. "android.hardware.light").
      * @hide
      */
     @TestApi
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 114de23..9cc9aac 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,5 +1,6 @@
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
@@ -205,7 +206,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o instanceof WorkSource) {
             WorkSource other = (WorkSource) o;
 
@@ -989,6 +990,7 @@
             mTags = tags;
         }
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder result = new StringBuilder("WorkChain{");
@@ -1015,7 +1017,7 @@
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (o instanceof WorkChain) {
                 WorkChain other = (WorkChain) o;
 
diff --git a/core/java/android/os/telephony/TelephonyRegistryManager.java b/core/java/android/os/telephony/TelephonyRegistryManager.java
new file mode 100644
index 0000000..459c414
--- /dev/null
+++ b/core/java/android/os/telephony/TelephonyRegistryManager.java
@@ -0,0 +1,546 @@
+/*
+ * 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 android.os.telephony;
+
+import android.annotation.SystemApi;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.CallQuality;
+import android.telephony.CellInfo;
+import android.telephony.DataFailCause;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneCapability;
+import android.telephony.PreciseCallState.State;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.CallState;
+import android.telephony.TelephonyManager.DataActivityType;
+import android.telephony.TelephonyManager.DataState;
+import android.telephony.TelephonyManager.NetworkType;
+import android.telephony.TelephonyManager.RadioPowerState;
+import android.telephony.TelephonyManager.SimActivationState;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
+import android.telephony.ims.ImsReasonInfo;
+import com.android.internal.telephony.ITelephonyRegistry;
+import java.util.List;
+
+/**
+ * A centralized place to notify telephony related status changes, e.g, {@link ServiceState} update
+ * or {@link PhoneCapability} changed. This might trigger callback from applications side through
+ * {@link android.telephony.PhoneStateListener}
+ *
+ * TODO: limit API access to only carrier apps with certain permissions or apps running on
+ * privileged UID.
+ *
+ * @hide
+ */
+@SystemApi
+public class TelephonyRegistryManager {
+
+    private static final String TAG = "TelephonyRegistryManager";
+    private static ITelephonyRegistry sRegistry;
+
+    /** @hide **/
+    public TelephonyRegistryManager() {
+        if (sRegistry == null) {
+            sRegistry = ITelephonyRegistry.Stub.asInterface(
+                ServiceManager.getService("telephony.registry"));
+        }
+    }
+
+    /**
+     * Informs the system of an intentional upcoming carrier network change by a carrier app.
+     * 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 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(boolean active) {
+        try {
+            sRegistry.notifyCarrierNetworkChange(active);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
+     * Notify call state changed on certain subscription.
+     *
+     * @param subId for which call state changed.
+     * @param slotIndex for which call state changed. Can be derived from subId except when subId is
+     * invalid.
+     * @param state latest call state. e.g, offhook, ringing
+     * @param incomingNumer incoming phone number.
+     *
+     * @hide
+     */
+    public void notifyCallStateChanged(int subId, int slotIndex, @CallState int state,
+        String incomingNumer) {
+        try {
+            sRegistry.notifyCallState(slotIndex, subId, state, incomingNumer);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
+     * Notify {@link ServiceState} update on certain subscription.
+     *
+     * @param subId for which the service state changed.
+     * @param slotIndex for which the service state changed. Can be derived from subId except
+     * subId is invalid.
+     * @param state service state e.g, in service, out of service or roaming status.
+     *
+     * @hide
+     */
+    public void notifyServiceStateChanged(int subId, int slotIndex, ServiceState state) {
+        try {
+            sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
+     * Notify {@link SignalStrength} update on certain subscription.
+     *
+     * @param subId for which the signalstrength changed.
+     * @param slotIndex for which the signalstrength changed. Can be derived from subId except when
+     * subId is invalid.
+     * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
+     *
+     * @hide
+     */
+    public void notifySignalStrengthChanged(int subId, int slotIndex,
+        SignalStrength signalStrength) {
+        try {
+            sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
+     * Notify changes to the message-waiting indicator on certain subscription. e.g, The status bar
+     * uses message waiting indicator to determine when to display the voicemail icon.
+     *
+     * @param subId for which message waiting indicator changed.
+     * @param slotIndex for which message waiting indicator changed. Can be derived from subId
+     * except when subId is invalid.
+     * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
+     * otherwise.
+     *
+     * @hide
+     */
+    public void notifyMessageWaitingChanged(int subId, int slotIndex, boolean msgWaitingInd) {
+        try {
+            sRegistry.notifyMessageWaitingChangedForPhoneId(slotIndex, subId, msgWaitingInd);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify changes to the call-forwarding status on certain subscription.
+     *
+     * @param subId for which call forwarding status changed.
+     * @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
+     * otherwise.
+     *
+     * @hide
+     */
+    public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
+        try {
+            sRegistry.notifyCallForwardingChangedForSubscriber(subId, callForwardInd);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify changes to activity state changes on certain subscription.
+     *
+     * @param subId for which data activity state changed.
+     * @param dataActivityType indicates the latest data activity type e.g, {@link
+     * TelephonyManager#DATA_ACTIVITY_IN}
+     *
+     * @hide
+     */
+    public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
+        try {
+            sRegistry.notifyDataActivityForSubscriber(subId, dataActivityType);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify changes to default (Internet) data connection state on certain subscription.
+     *
+     * @param subId for which data connection state changed.
+     * @param slotIndex for which data connections state changed. Can be derived from subId except
+     * when subId is invalid.
+     * @param state latest data connection state, e.g,
+     * @param isDataConnectivityPossible indicates if data is allowed
+     * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
+     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
+     * @param linkProperties {@link LinkProperties} associated with this data connection.
+     * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
+     * @param networkType associated with this data connection.
+     * @param roaming {@code true} indicates in roaming, {@false} otherwise.
+     * @see TelephonyManager#DATA_DISCONNECTED
+     * @see TelephonyManager#isDataConnectivityPossible()
+     *
+     * @hide
+     */
+    public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
+        boolean isDataConnectivityPossible,
+        @ApnType String apn, String apnType, LinkProperties linkProperties,
+        NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+        try {
+            sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
+                isDataConnectivityPossible,
+                apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify {@link CallQuality} change on certain subscription.
+     *
+     * @param subId for which call quality state changed.
+     * @param slotIndex for which call quality state changed. Can be derived from subId except when
+     * subId is invalid.
+     * @param callQuality Information about call quality e.g, call quality level
+     * @param networkType associated with this data connection. e.g, LTE
+     *
+     * @hide
+     */
+    public void notifyCallQualityChanged(int subId, int slotIndex, CallQuality callQuality,
+        @NetworkType int networkType) {
+        try {
+            sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify emergency number list changed on certain subscription.
+     *
+     * @param subId for which emergency number list changed.
+     * @param slotIndex for which emergency number list changed. Can be derived from subId except
+     * when subId is invalid.
+     *
+     * @hide
+     */
+    public void notifyEmergencyNumberList(int subId, int slotIndex) {
+        try {
+            sRegistry.notifyEmergencyNumberList(slotIndex, subId);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify radio power state changed on certain subscription.
+     *
+     * @param subId for which radio power state changed.
+     * @param slotIndex for which radio power state changed. Can be derived from subId except when
+     * subId is invalid.
+     * @param radioPowerState the current modem radio state.
+     *
+     * @hide
+     */
+    public void notifyRadioPowerStateChanged(int subId, int slotIndex,
+        @RadioPowerState int radioPowerState) {
+        try {
+            sRegistry.notifyRadioPowerStateChanged(slotIndex, subId, radioPowerState);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify {@link PhoneCapability} changed.
+     *
+     * @param phoneCapability the capability of the modem group.
+     *
+     * @hide
+     */
+    public void notifyPhoneCapabilityChanged(PhoneCapability phoneCapability) {
+        try {
+            sRegistry.notifyPhoneCapabilityChanged(phoneCapability);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify data activation state changed on certain subscription.
+     * @see TelephonyManager#getDataActivationState()
+     *
+     * @param subId for which data activation state changed.
+     * @param slotIndex for which data activation state changed. Can be derived from subId except
+     * when subId is invalid.
+     * @param activationState sim activation state e.g, activated.
+     *
+     * @hide
+     */
+    public void notifyDataActivationStateChanged(int subId, int slotIndex,
+        @SimActivationState int activationState) {
+        try {
+            sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId,
+                TelephonyManager.SIM_ACTIVATION_TYPE_DATA, activationState);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify voice activation state changed on certain subscription.
+     * @see TelephonyManager#getVoiceActivationState()
+     *
+     * @param subId for which voice activation state changed.
+     * @param slotIndex for which voice activation state changed. Can be derived from subId except
+     * subId is invalid.
+     * @param activationState sim activation state e.g, activated.
+     *
+     * @hide
+     */
+    public void notifyVoiceActivationStateChanged(int subId, int slotIndex,
+        @SimActivationState int activationState) {
+        try {
+            sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId,
+                TelephonyManager.SIM_ACTIVATION_TYPE_VOICE, activationState);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify User mobile data state changed on certain subscription. e.g, mobile data is enabled
+     * or disabled.
+     *
+     * @param subId for which mobile data state has changed.
+     * @param slotIndex for which mobile data state has changed. Can be derived from subId except
+     * when subId is invalid.
+     * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
+     *
+     * @hide
+     */
+    public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
+        try {
+            sRegistry.notifyUserMobileDataStateChangedForPhoneId(slotIndex, subId, state);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * TODO: this is marked as deprecated, can we move this one safely?
+     *
+     * @param subId
+     * @param slotIndex
+     * @param rawData
+     *
+     * @hide
+     */
+    public void notifyOemHookRawEventForSubscriber(int subId, int slotIndex, byte[] rawData) {
+        try {
+            sRegistry.notifyOemHookRawEventForSubscriber(slotIndex, subId, rawData);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify IMS call disconnect causes which contains {@link android.telephony.ims.ImsReasonInfo}.
+     *
+     * @param subId for which ims call disconnect.
+     * @param imsReasonInfo the reason for ims call disconnect.
+     *
+     * @hide
+     */
+    public void notifyImsDisconnectCause(int subId, ImsReasonInfo imsReasonInfo) {
+        try {
+            sRegistry.notifyImsDisconnectCause(subId, imsReasonInfo);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify precise data connection failed cause on certain subscription.
+     *
+     * @param subId for which data connection failed.
+     * @param slotIndex for which data conenction failed. Can be derived from subId except when
+     * subId is invalid.
+     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
+     * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
+     * @param failCause data fail cause.
+     *
+     * @hide
+     */
+    public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, String apnType,
+        String apn, @DataFailCause.FailCause int failCause) {
+        try {
+            sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify single Radio Voice Call Continuity (SRVCC) state change for the currently active call
+     * on certain subscription.
+     *
+     * @param subId for which srvcc state changed.
+     * @param state srvcc state
+     *
+     * @hide
+     */
+    public void notifySrvccStateChanged(int subId, @TelephonyManager.SrvccState int state) {
+        try {
+            sRegistry.notifySrvccStateChanged(subId, state);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify over the air sim provisioning(OTASP) mode changed on certain subscription.
+     *
+     * @param subId for which otasp mode changed.
+     * @param otaspMode latest mode for OTASP e.g, OTASP needed.
+     *
+     * @hide
+     */
+    public void notifyOtaspChanged(int subId, int otaspMode) {
+        try {
+            sRegistry.notifyOtaspChanged(subId, otaspMode);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify precise call state changed on certain subscription, including foreground, background
+     * and ringcall states.
+     *
+     * @param subId for which precise call state changed.
+     * @param slotIndex for which precise call state changed. Can be derived from subId except when
+     * subId is invalid.
+     * @param ringCallPreciseState ringCall state.
+     * @param foregroundCallPreciseState foreground call state.
+     * @param backgroundCallPreciseState background call state.
+     *
+     * @hide
+     */
+    public void notifyPreciseCallState(int subId, int slotIndex, @State int ringCallPreciseState,
+        @State int foregroundCallPreciseState, @State int backgroundCallPreciseState) {
+        try {
+            sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
+                foregroundCallPreciseState, backgroundCallPreciseState);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify call disconnect causes which contains {@link DisconnectCause} and {@link
+     * android.telephony.PreciseDisconnectCause}.
+     *
+     * @param subId for which call disconnected.
+     * @param slotIndex for which call disconnected. Can be derived from subId except when subId is
+     * invalid.
+     * @param cause {@link DisconnectCause} for the disconnected call.
+     * @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
+     * call.
+     *
+     * @hide
+     */
+    public void notifyDisconnectCause(int slotIndex, int subId, int cause, int preciseCause) {
+        try {
+            sRegistry.notifyDisconnectCause(slotIndex, subId, cause, preciseCause);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify data connection failed on certain subscription.
+     *
+     * @param subId for which data connection failed.
+     * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
+     * is invalid.
+     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
+     * connection can support multiple anyTypes.
+     *
+     * @hide
+     */
+    public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
+        try {
+            sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * TODO change from bundle to CellLocation?
+     * @hide
+     */
+    public void notifyCellLocation(int subId, Bundle cellLocation) {
+        try {
+            sRegistry.notifyCellLocationForSubscriber(subId, cellLocation);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    /**
+     * Notify {@link CellInfo} changed on certain subscription. e.g, when an observed cell info has
+     * changed or new cells have been added or removed on the given subscription.
+     *
+     * @param subId for which cellinfo changed.
+     * @param cellInfo A list of cellInfo associated with the given subscription.
+     *
+     * @hide
+     */
+    public void notifyCellInfoChanged(int subId, List<CellInfo> cellInfo) {
+        try {
+            sRegistry.notifyCellInfoForSubscriber(subId, cellInfo);
+        } catch (RemoteException ex) {
+
+        }
+    }
+
+}
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index ec0fe92..654b0f7 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -42,4 +42,5 @@
     void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
                 String permission, int grantState, in AndroidFuture callback);
     void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
+    void updateUserSensitive(in AndroidFuture callback);
 }
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 9fa5f16..60c8811 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -19,6 +19,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
 
 /**
@@ -97,4 +98,6 @@
             String packageName, int userId);
 
     boolean isPermissionRevokedByPolicy(String permName, String packageName, int userId);
+
+    List<SplitPermissionInfoParcelable> getSplitPermissions();
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index d012ac7..923d9f8 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -599,4 +599,16 @@
             }
         }, executor);
     }
+
+    /**
+     * @see PermissionControllerService#onUpdateUserSensitive()
+     * @hide
+     */
+    public void updateUserSensitive() {
+        mRemoteService.postAsync(service -> {
+            AndroidFuture<Void> future = new AndroidFuture<>();
+            service.updateUserSensitive(future);
+            return future;
+        });
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 6dd9b97..7363d77 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -190,6 +190,18 @@
     public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback);
 
     /**
+     * Called by system to update the
+     * {@link PackageManager}{@code .FLAG_PERMISSION_USER_SENSITIVE_WHEN_*} flags for permissions.
+     * <p>
+     * This is typically when creating a new user or upgrading either system or
+     * permission controller package.
+     */
+    @BinderThread
+    public void onUpdateUserSensitive() {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
+    /**
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -380,6 +392,14 @@
 
                 onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(true));
             }
+
+            @Override
+            public void updateUserSensitive(AndroidFuture callback) {
+                Preconditions.checkNotNull(callback, "callback cannot be null");
+
+                onUpdateUserSensitive();
+                callback.complete(null);
+            }
         };
     }
 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 42816c0..6c4ee01 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -19,20 +19,23 @@
 import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import com.android.internal.annotations.Immutable;
-import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * System level service for accessing the permission capabilities of the platform.
@@ -43,6 +46,8 @@
 @SystemApi
 @SystemService(Context.PERMISSION_SERVICE)
 public final class PermissionManager {
+    private static final String TAG = PermissionManager.class.getName();
+
     /** @hide */
     public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
             "permissions revoked";
@@ -50,19 +55,12 @@
     public static final String KILL_APP_REASON_GIDS_CHANGED =
             "permission grant or revoke changed gids";
 
-
-    /**
-     * {@link android.content.pm.PackageParser} needs access without having a {@link Context}.
-     *
-     * @hide
-     */
-    public static final ArrayList<SplitPermissionInfo> SPLIT_PERMISSIONS =
-            SystemConfig.getInstance().getSplitPermissions();
-
     private final @NonNull Context mContext;
 
     private final IPackageManager mPackageManager;
 
+    private List<SplitPermissionInfo> mSplitPermissionInfos;
+
     /**
      * Creates a new instance.
      *
@@ -130,7 +128,48 @@
      * @return All permissions that are split.
      */
     public @NonNull List<SplitPermissionInfo> getSplitPermissions() {
-        return SPLIT_PERMISSIONS;
+        if (mSplitPermissionInfos != null) {
+            return mSplitPermissionInfos;
+        }
+
+        List<SplitPermissionInfoParcelable> parcelableList;
+        try {
+            parcelableList = ActivityThread.getPermissionManager().getSplitPermissions();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error getting split permissions", e);
+            return Collections.emptyList();
+        }
+
+        mSplitPermissionInfos = splitPermissionInfoListToNonParcelableList(parcelableList);
+
+        return mSplitPermissionInfos;
+    }
+
+    private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
+            List<SplitPermissionInfoParcelable> parcelableList) {
+        final int size = parcelableList.size();
+        List<SplitPermissionInfo> list = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            list.add(new SplitPermissionInfo(parcelableList.get(i)));
+        }
+        return list;
+    }
+
+    /**
+     * Converts a {@link List} of {@link SplitPermissionInfo} into a List of
+     * {@link SplitPermissionInfoParcelable} and returns it.
+     * @hide
+     */
+    public static List<SplitPermissionInfoParcelable> splitPermissionInfoListToParcelableList(
+            List<SplitPermissionInfo> splitPermissionsList) {
+        final int size = splitPermissionsList.size();
+        List<SplitPermissionInfoParcelable> outList = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            SplitPermissionInfo info = splitPermissionsList.get(i);
+            outList.add(new SplitPermissionInfoParcelable(
+                    info.getSplitPermission(), info.getNewPermissions(), info.getTargetSdk()));
+        }
+        return outList;
     }
 
     /**
@@ -139,44 +178,40 @@
      */
     @Immutable
     public static final class SplitPermissionInfo {
-        private final @NonNull String mSplitPerm;
-        private final @NonNull List<String> mNewPerms;
-        private final int mTargetSdk;
+        private @NonNull final SplitPermissionInfoParcelable mSplitPermissionInfoParcelable;
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             SplitPermissionInfo that = (SplitPermissionInfo) o;
-            return mTargetSdk == that.mTargetSdk
-                    && mSplitPerm.equals(that.mSplitPerm)
-                    && mNewPerms.equals(that.mNewPerms);
+            return mSplitPermissionInfoParcelable.equals(that.mSplitPermissionInfoParcelable);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mSplitPerm, mNewPerms, mTargetSdk);
+            return mSplitPermissionInfoParcelable.hashCode();
         }
 
         /**
          * Get the permission that is split.
          */
         public @NonNull String getSplitPermission() {
-            return mSplitPerm;
+            return mSplitPermissionInfoParcelable.getSplitPermission();
         }
 
         /**
          * Get the permissions that are added.
          */
         public @NonNull List<String> getNewPermissions() {
-            return mNewPerms;
+            return mSplitPermissionInfoParcelable.getNewPermissions();
         }
 
         /**
          * Get the target API level when the permission was split.
          */
         public int getTargetSdk() {
-            return mTargetSdk;
+            return mSplitPermissionInfoParcelable.getTargetSdk();
         }
 
         /**
@@ -190,9 +225,11 @@
          */
         public SplitPermissionInfo(@NonNull String splitPerm, @NonNull List<String> newPerms,
                 int targetSdk) {
-            mSplitPerm = splitPerm;
-            mNewPerms = newPerms;
-            mTargetSdk = targetSdk;
+            this(new SplitPermissionInfoParcelable(splitPerm, newPerms, targetSdk));
+        }
+
+        private SplitPermissionInfo(@NonNull SplitPermissionInfoParcelable parcelable) {
+            mSplitPermissionInfoParcelable = parcelable;
         }
     }
 }
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 565843e..0c1b61d 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -17,6 +17,7 @@
 package android.printservice;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
@@ -292,7 +293,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -313,6 +314,7 @@
         return true;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0827fd6..0973a64 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -42,7 +42,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.CallerInfo;
+import android.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
 
 import java.util.List;
@@ -728,10 +728,11 @@
             String accountAddress = getLogAccountAddress(context, accountHandle);
 
             int numberPresentation = getLogNumberPresentation(number, presentation);
+            String name = (ci != null) ? ci.getName() : "";
             if (numberPresentation != PRESENTATION_ALLOWED) {
                 number = "";
                 if (ci != null) {
-                    ci.name = "";
+                    name = "";
                 }
             }
 
@@ -760,9 +761,7 @@
             values.put(PHONE_ACCOUNT_ID, accountId);
             values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
             values.put(NEW, Integer.valueOf(1));
-            if ((ci != null) && (ci.name != null)) {
-                values.put(CACHED_NAME, ci.name);
-            }
+            values.put(CACHED_NAME, name);
             values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
 
             if (callType == MISSED_TYPE) {
@@ -773,7 +772,7 @@
             values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
             values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
 
-            if ((ci != null) && (ci.contactIdOrZero > 0)) {
+            if ((ci != null) && (ci.getContactId() > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
                 // contacts may have the same number.
@@ -787,17 +786,18 @@
                     cursor = resolver.query(Phone.CONTENT_URI,
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
-                            new String[] { String.valueOf(ci.contactIdOrZero),
+                            new String[] { String.valueOf(ci.getContactId()),
                                     normalizedPhoneNumber},
                             null);
                 } else {
-                    final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
+                    final String phoneNumber = ci.getPhoneNumber() != null
+                        ? ci.getPhoneNumber() : number;
                     cursor = resolver.query(
                             Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
                                     Uri.encode(phoneNumber)),
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =?",
-                            new String[] { String.valueOf(ci.contactIdOrZero) },
+                            new String[] { String.valueOf(ci.getContactId()) },
                             null);
                 }
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index b7676b6..5e201e4 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -367,15 +367,6 @@
          */
         String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
                 "system_gesture_exclusion_log_debounce_millis";
-
-        /**
-         * Key for controlling which packages are explicitly blocked from running at refresh rates
-         * higher than 90hz.
-         *
-         * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
-         * @hide
-         */
-        String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
     }
 
     private static final Object sLock = new Object();
@@ -775,8 +766,9 @@
          *
          * @param namespace The namespace these properties belong to.
          * @param keyValueMap A map between property names and property values.
+         * @hide
          */
-        Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
+        public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
             Preconditions.checkNotNull(namespace);
             mNamespace = namespace;
             mMap = new HashMap();
diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
index a60be53..87f9af3 100644
--- a/core/java/android/provider/SearchIndexableData.java
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -139,6 +140,7 @@
         context = ctx;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
index 1eb1734..0765b6b 100644
--- a/core/java/android/provider/SearchIndexableResource.java
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -16,6 +16,7 @@
 
 package android.provider;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -66,6 +67,7 @@
         super(context);
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 5f8266d..298628e 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -84,9 +84,9 @@
     /**
      * Last path segment for Preference Key, Slice Uri pair.
      * <p>
-     *     The (Key, Slice Uri) pairs are a mapping between the primary key of the search result and
-     *     a Uri for a Slice that represents the same data. Thus, an app can specify a list of Uris
-     *     for Slices that replace regular intent-based search results with inline content.
+     * The (Key, Slice Uri) pairs are a mapping between the primary key of the search result and
+     * a Uri for a Slice that represents the same data. Thus, an app can specify a list of Uris
+     * for Slices that replace regular intent-based search results with inline content.
      * </p>
      */
     public static final String SLICE_URI_PAIRS = "slice_uri_pairs";
@@ -96,6 +96,22 @@
      */
     public static final String SLICE_URI_PAIRS_PATH = SETTINGS + "/" + SLICE_URI_PAIRS;
 
+
+    /**
+     * Dynamic indexable raw data names.
+     *
+     * @hide
+     */
+    public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
+
+    /**
+     * ContentProvider path for dynamic indexable raw data.
+     *
+     * @hide
+     */
+    public static final String DYNAMIC_INDEXABLES_RAW_PATH =
+            SETTINGS + "/" + DYNAMIC_INDEXABLES_RAW;
+
     /**
      * Indexable xml resources columns.
      */
@@ -212,7 +228,7 @@
      * Cursor schema for SliceUriPairs.
      */
     @NonNull
-    public static final String[] SLICE_URI_PAIRS_COLUMNS = new String[]{
+    public static final String[] SLICE_URI_PAIRS_COLUMNS = new String[] {
             SliceUriPairColumns.KEY,
             SliceUriPairColumns.SLICE_URI
     };
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index da29e2e..68284b4 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -77,6 +77,7 @@
     private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3;
     private static final int MATCH_SITE_MAP_PAIRS_CODE = 4;
     private static final int MATCH_SLICE_URI_PAIRS_CODE = 5;
+    private static final int MATCH_DYNAMIC_RAW_CODE = 6;
 
     /**
      * Implementation is provided by the parent class.
@@ -96,6 +97,8 @@
                 MATCH_SITE_MAP_PAIRS_CODE);
         mMatcher.addURI(mAuthority, SearchIndexablesContract.SLICE_URI_PAIRS_PATH,
                 MATCH_SLICE_URI_PAIRS_CODE);
+        mMatcher.addURI(mAuthority, SearchIndexablesContract.DYNAMIC_INDEXABLES_RAW_PATH,
+                MATCH_DYNAMIC_RAW_CODE);
 
         // Sanity check our setup
         if (!info.exported) {
@@ -126,6 +129,8 @@
                     return querySiteMapPairs();
                 case MATCH_SLICE_URI_PAIRS_CODE:
                     return querySliceUriPairs();
+                case MATCH_DYNAMIC_RAW_CODE:
+                    return queryDynamicRawData(null);
                 default:
                     throw new UnsupportedOperationException("Unknown Uri " + uri);
             }
@@ -191,12 +196,30 @@
         return null;
     }
 
+    /**
+     * Returns all {@link android.provider.SearchIndexablesContract.RawData}.
+     *
+     * Those are the dynamic raw indexable data.
+     *
+     * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns
+     *                   to put into the cursor. If {@code null} all supported columns should be
+     *                   included.
+     *
+     * @hide
+     */
+    @Nullable
+    public Cursor queryDynamicRawData(String[] projection) {
+        // By default no-op;
+        return null;
+    }
+
     @Override
     public String getType(Uri uri) {
         switch (mMatcher.match(uri)) {
             case MATCH_RES_CODE:
                 return SearchIndexablesContract.XmlResource.MIME_TYPE;
             case MATCH_RAW_CODE:
+            case MATCH_DYNAMIC_RAW_CODE:
                 return SearchIndexablesContract.RawData.MIME_TYPE;
             case MATCH_NON_INDEXABLE_KEYS_CODE:
                 return SearchIndexablesContract.NonIndexableKey.MIME_TYPE;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f573d87..61d8eb1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,20 +16,6 @@
 
 package android.provider;
 
-import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.TILE_LIST_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
-import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR;
-
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -63,9 +49,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.SQLException;
-import android.hardware.display.ColorDisplayManager;
 import android.location.LocationManager;
-import android.media.AudioFormat;
 import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.net.Uri;
@@ -83,12 +67,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.provider.settings.validators.ComponentNameListValidator;
-import android.provider.settings.validators.DiscreteValueValidator;
-import android.provider.settings.validators.InclusiveFloatRangeValidator;
-import android.provider.settings.validators.InclusiveIntegerRangeValidator;
-import android.provider.settings.validators.PackageNameListValidator;
-import android.provider.settings.validators.Validator;
 import android.speech.tts.TextToSpeech;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
@@ -106,7 +84,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
-import java.text.SimpleDateFormat;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
@@ -2126,10 +2103,6 @@
     private static final String TAG = "Settings";
     private static final boolean LOCAL_LOGV = false;
 
-    // Lock ensures that when enabling/disabling the master location switch, we don't end up
-    // with a partial enable/disable state in multi-threaded situations.
-    private static final Object mLocationSettingsLock = new Object();
-
     // Used in system server calling uid workaround in call()
     private static boolean sInSystemServer = false;
     private static final Object sInSystemServerLock = new Object();
@@ -3120,30 +3093,6 @@
         @Deprecated
         public static final String STAY_ON_WHILE_PLUGGED_IN = Global.STAY_ON_WHILE_PLUGGED_IN;
 
-        private static final Validator STAY_ON_WHILE_PLUGGED_IN_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(String value) {
-                try {
-                    int val = Integer.parseInt(value);
-                    return (val == 0)
-                            || (val == BatteryManager.BATTERY_PLUGGED_AC)
-                            || (val == BatteryManager.BATTERY_PLUGGED_USB)
-                            || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_USB))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_USB
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_USB
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS));
-                } catch (NumberFormatException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * What happens when the user presses the end call button if they're not
          * on a call.<br/>
@@ -3156,9 +3105,6 @@
          */
         public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
 
-        private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 3);
-
         /**
          * END_BUTTON_BEHAVIOR value for "go home".
          * @hide
@@ -3183,8 +3129,6 @@
          */
         public static final String ADVANCED_SETTINGS = "advanced_settings";
 
-        private static final Validator ADVANCED_SETTINGS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * ADVANCED_SETTINGS default value.
          * @hide
@@ -3285,8 +3229,6 @@
         @Deprecated
         public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
 
-        private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The static IP address.
          * <p>
@@ -3297,8 +3239,6 @@
         @Deprecated
         public static final String WIFI_STATIC_IP = "wifi_static_ip";
 
-        private static final Validator WIFI_STATIC_IP_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
-
         /**
          * If using static IP, the gateway's IP address.
          * <p>
@@ -3309,8 +3249,6 @@
         @Deprecated
         public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
 
-        private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
-
         /**
          * If using static IP, the net mask.
          * <p>
@@ -3321,8 +3259,6 @@
         @Deprecated
         public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
 
-        private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
-
         /**
          * If using static IP, the primary DNS's IP address.
          * <p>
@@ -3333,8 +3269,6 @@
         @Deprecated
         public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
 
-        private static final Validator WIFI_STATIC_DNS1_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
-
         /**
          * If using static IP, the secondary DNS's IP address.
          * <p>
@@ -3345,8 +3279,6 @@
         @Deprecated
         public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
 
-        private static final Validator WIFI_STATIC_DNS2_VALIDATOR = LENIENT_IP_ADDRESS_VALIDATOR;
-
         /**
          * Determines whether remote devices may discover and/or connect to
          * this device.
@@ -3358,9 +3290,6 @@
         public static final String BLUETOOTH_DISCOVERABILITY =
             "bluetooth_discoverability";
 
-        private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 2);
-
         /**
          * Bluetooth discoverability timeout.  If this value is nonzero, then
          * Bluetooth becomes discoverable for a certain number of seconds,
@@ -3369,9 +3298,6 @@
         public static final String BLUETOOTH_DISCOVERABILITY_TIMEOUT =
             "bluetooth_discoverability_timeout";
 
-        private static final Validator BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
          * instead
@@ -3404,32 +3330,11 @@
         @Deprecated
         public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
 
-        private static final Validator NEXT_ALARM_FORMATTED_VALIDATOR = new Validator() {
-            private static final int MAX_LENGTH = 1000;
-
-            @Override
-            public boolean validate(String value) {
-                // TODO: No idea what the correct format is.
-                return value == null || value.length() < MAX_LENGTH;
-            }
-        };
-
         /**
          * Scaling factor for fonts, float.
          */
         public static final String FONT_SCALE = "font_scale";
 
-        private static final Validator FONT_SCALE_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                try {
-                    return Float.parseFloat(value) >= 0;
-                } catch (NumberFormatException | NullPointerException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * The serialized system locale value.
          *
@@ -3466,34 +3371,12 @@
         @Deprecated
         public static final String DIM_SCREEN = "dim_screen";
 
-        private static final Validator DIM_SCREEN_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The display color mode.
          * @hide
          */
         public static final String DISPLAY_COLOR_MODE = "display_color_mode";
 
-        private static final Validator DISPLAY_COLOR_MODE_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                // Assume the actual validation that this device can properly handle this kind of
-                // color mode further down in ColorDisplayManager / ColorDisplayService.
-                try {
-                    final int setting = Integer.parseInt(value);
-                    final boolean isInFrameworkRange =
-                            setting >= ColorDisplayManager.COLOR_MODE_NATURAL
-                                    && setting <= ColorDisplayManager.COLOR_MODE_AUTOMATIC;
-                    final boolean isInVendorRange =
-                            setting >= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN
-                                    && setting <= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX;
-                    return isInFrameworkRange || isInVendorRange;
-                } catch (NumberFormatException | NullPointerException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * The user selected min refresh rate in frames per second.
          *
@@ -3510,9 +3393,6 @@
          */
         public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
 
-        private static final Validator PEAK_REFRESH_RATE_VALIDATOR =
-                new InclusiveFloatRangeValidator(24f, Float.MAX_VALUE);
-
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
@@ -3525,9 +3405,6 @@
          */
         public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
 
-        private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * The screen backlight brightness between 0 and 255.
          */
@@ -3539,16 +3416,11 @@
          */
         public static final String SCREEN_BRIGHTNESS_FOR_VR = "screen_brightness_for_vr";
 
-        private static final Validator SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 255);
-
         /**
          * Control whether to enable automatic brightness mode.
          */
         public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
 
-        private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
          * or less (<0.0 >-1.0) bright.
@@ -3557,9 +3429,6 @@
         @UnsupportedAppUsage
         public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
 
-        private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
-                new InclusiveFloatRangeValidator(-1, 1);
-
         /**
          * SCREEN_BRIGHTNESS_MODE value for manual mode.
          */
@@ -3576,8 +3445,6 @@
          */
         public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
 
-        private static final Validator ADAPTIVE_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Control whether the process CPU usage meter should be shown.
          *
@@ -3604,9 +3471,6 @@
          */
         public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
 
-        private static final Validator MODE_RINGER_STREAMS_AFFECTED_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
           * Determines which streams are affected by mute. The
           * stream type's bit should be set to 1 if it should be muted when a mute request
@@ -3614,17 +3478,12 @@
           */
         public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
 
-        private static final Validator MUTE_STREAMS_AFFECTED_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Whether vibrate is on for different events. This is used internally,
          * changing this value will not change the vibrate. See AudioManager.
          */
         public static final String VIBRATE_ON = "vibrate_on";
 
-        private static final Validator VIBRATE_ON_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * If 1, redirects the system vibrator to all currently attached input devices
          * that support vibration.  If there are no such input devices, then the system
@@ -3639,8 +3498,6 @@
          */
         public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
 
-        private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The intensity of notification vibrations, if configurable.
          *
@@ -3691,9 +3548,6 @@
         public static final String HAPTIC_FEEDBACK_INTENSITY =
                 "haptic_feedback_intensity";
 
-        private static final Validator VIBRATION_INTENSITY_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 3);
-
         /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
@@ -3772,8 +3626,6 @@
         @UnsupportedAppUsage
         public static final String MASTER_MONO = "master_mono";
 
-        private static final Validator MASTER_MONO_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Master balance (float -1.f = 100% left, 0.f = dead center, 1.f = 100% right).
          *
@@ -3781,9 +3633,6 @@
          */
         public static final String MASTER_BALANCE = "master_balance";
 
-        private static final Validator MASTER_BALANCE_VALIDATOR =
-                new InclusiveFloatRangeValidator(-1.f, 1.f);
-
         /**
          * Whether the notifications should use the ring volume (value of 1) or
          * a separate notification volume (value of 0). In most cases, users
@@ -3802,8 +3651,6 @@
         public static final String NOTIFICATIONS_USE_RING_VOLUME =
             "notifications_use_ring_volume";
 
-        private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether silent mode should allow vibration feedback. This is used
          * internally in AudioService and the Sound settings activity to
@@ -3819,8 +3666,6 @@
         @UnsupportedAppUsage
         public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
 
-        private static final Validator VIBRATE_IN_SILENT_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The mapping of stream type (integer) to its setting.
          *
@@ -3867,8 +3712,6 @@
          */
         public static final String RINGTONE = "ringtone";
 
-        private static final Validator RINGTONE_VALIDATOR = URI_VALIDATOR;
-
         /**
          * A {@link Uri} that will point to the current default ringtone at any
          * given time.
@@ -3892,8 +3735,6 @@
          */
         public static final String NOTIFICATION_SOUND = "notification_sound";
 
-        private static final Validator NOTIFICATION_SOUND_VALIDATOR = URI_VALIDATOR;
-
         /**
          * A {@link Uri} that will point to the current default notification
          * sound at any given time.
@@ -3915,8 +3756,6 @@
          */
         public static final String ALARM_ALERT = "alarm_alert";
 
-        private static final Validator ALARM_ALERT_VALIDATOR = URI_VALIDATOR;
-
         /**
          * A {@link Uri} that will point to the current default alarm alert at
          * any given time.
@@ -3937,42 +3776,30 @@
          */
         public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
 
-        private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = COMPONENT_NAME_VALIDATOR;
-
         /**
          * Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
          */
         public static final String TEXT_AUTO_REPLACE = "auto_replace";
 
-        private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
          */
         public static final String TEXT_AUTO_CAPS = "auto_caps";
 
-        private static final Validator TEXT_AUTO_CAPS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
          * feature converts two spaces to a "." and space.
          */
         public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
 
-        private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting to showing password characters in text editors. 1 = On, 0 = Off
          */
         public static final String TEXT_SHOW_PASSWORD = "show_password";
 
-        private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = BOOLEAN_VALIDATOR;
-
         public static final String SHOW_GTALK_SERVICE_STATUS =
                 "SHOW_GTALK_SERVICE_STATUS";
 
-        private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Name of activity to use for wallpaper on the home screen.
          *
@@ -3981,18 +3808,6 @@
         @Deprecated
         public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
 
-        private static final Validator WALLPAPER_ACTIVITY_VALIDATOR = new Validator() {
-            private static final int MAX_LENGTH = 1000;
-
-            @Override
-            public boolean validate(String value) {
-                if (value != null && value.length() > MAX_LENGTH) {
-                    return false;
-                }
-                return ComponentName.unflattenFromString(value) != null;
-            }
-        };
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME}
          * instead
@@ -4000,8 +3815,6 @@
         @Deprecated
         public static final String AUTO_TIME = Global.AUTO_TIME;
 
-        private static final Validator AUTO_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
          * instead
@@ -4009,8 +3822,6 @@
         @Deprecated
         public static final String AUTO_TIME_ZONE = Global.AUTO_TIME_ZONE;
 
-        private static final Validator AUTO_TIME_ZONE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Display times as 12 or 24 hours
          *   12
@@ -4018,10 +3829,6 @@
          */
         public static final String TIME_12_24 = "time_12_24";
 
-        /** @hide */
-        public static final Validator TIME_12_24_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"12", "24", null});
-
         /**
          * Date format string
          *   mm/dd/yyyy
@@ -4030,19 +3837,6 @@
          */
         public static final String DATE_FORMAT = "date_format";
 
-        /** @hide */
-        public static final Validator DATE_FORMAT_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                try {
-                    new SimpleDateFormat(value);
-                    return true;
-                } catch (IllegalArgumentException | NullPointerException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * Whether the setup wizard has been run before (on first boot), or if
          * it still needs to be run.
@@ -4052,9 +3846,6 @@
          */
         public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
 
-        /** @hide */
-        public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Scaling factor for normal window animations. Setting to 0 will disable window
          * animations.
@@ -4091,9 +3882,6 @@
          */
         public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
 
-        /** @hide */
-        public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Default screen rotation when no other policy applies.
          * When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
@@ -4104,10 +3892,6 @@
          */
         public static final String USER_ROTATION = "user_rotation";
 
-        /** @hide */
-        public static final Validator USER_ROTATION_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 3);
-
         /**
          * Control whether the rotation lock toggle in the System UI should be hidden.
          * Typically this is done for accessibility purposes to make it harder for
@@ -4123,10 +3907,6 @@
         public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
                 "hide_rotation_lock_toggle_for_accessibility";
 
-        /** @hide */
-        public static final Validator HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether the phone vibrates when it is ringing due to an incoming call. This will
          * be used by Phone and Setting apps; it shouldn't affect other apps.
@@ -4139,9 +3919,6 @@
          */
         public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
 
-        /** @hide */
-        public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * When {@code 1}, Telecom enhanced call blocking functionality is enabled.  When
          * {@code 0}, enhanced call blocking functionality is disabled.
@@ -4156,9 +3933,6 @@
          */
         public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
 
-        /** @hide */
-        public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * CDMA only settings
          * DTMF tone type played by the dialer when dialing.
@@ -4167,9 +3941,6 @@
          */
         public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
 
-        /** @hide */
-        public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the hearing aid is enabled. The value is
          * boolean (1 or 0).
@@ -4178,9 +3949,6 @@
         @UnsupportedAppUsage
         public static final String HEARING_AID = "hearing_aid";
 
-        /** @hide */
-        public static final Validator HEARING_AID_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * CDMA only settings
          * TTY Mode
@@ -4193,28 +3961,18 @@
         @UnsupportedAppUsage
         public static final String TTY_MODE = "tty_mode";
 
-        /** @hide */
-        public static final Validator TTY_MODE_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 3);
-
         /**
          * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
          * boolean (1 or 0).
          */
         public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
 
-        /** @hide */
-        public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether haptic feedback (Vibrate on tap) is enabled. The value is
          * boolean (1 or 0).
          */
         public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
 
-        /** @hide */
-        public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Each application that shows web suggestions should have its own
          * setting for this.
@@ -4222,9 +3980,6 @@
         @Deprecated
         public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
 
-        /** @hide */
-        public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the notification LED should repeatedly flash when a notification is
          * pending. The value is boolean (1 or 0).
@@ -4233,9 +3988,6 @@
         @UnsupportedAppUsage
         public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
 
-        /** @hide */
-        public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Show pointer location on screen?
          * 0 = no
@@ -4245,9 +3997,6 @@
         @UnsupportedAppUsage
         public static final String POINTER_LOCATION = "pointer_location";
 
-        /** @hide */
-        public static final Validator POINTER_LOCATION_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Show touch positions on screen?
          * 0 = no
@@ -4257,9 +4006,6 @@
         @UnsupportedAppUsage
         public static final String SHOW_TOUCHES = "show_touches";
 
-        /** @hide */
-        public static final Validator SHOW_TOUCHES_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Log raw orientation data from
          * {@link com.android.server.policy.WindowOrientationListener} for use with the
@@ -4271,9 +4017,6 @@
         public static final String WINDOW_ORIENTATION_LISTENER_LOG =
                 "window_orientation_listener_log";
 
-        /** @hide */
-        public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
          * instead
@@ -4282,8 +4025,6 @@
         @Deprecated
         public static final String POWER_SOUNDS_ENABLED = Global.POWER_SOUNDS_ENABLED;
 
-        private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#DOCK_SOUNDS_ENABLED}
          * instead
@@ -4293,8 +4034,6 @@
         @UnsupportedAppUsage
         public static final String DOCK_SOUNDS_ENABLED = Global.DOCK_SOUNDS_ENABLED;
 
-        private static final Validator DOCK_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether to play sounds when the keyguard is shown and dismissed.
          * @hide
@@ -4302,18 +4041,12 @@
         @UnsupportedAppUsage
         public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
 
-        /** @hide */
-        public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the lockscreen should be completely disabled.
          * @hide
          */
         public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
 
-        /** @hide */
-        public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
          * instead
@@ -4384,9 +4117,6 @@
          */
         public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
 
-        /** @hide */
-        public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Call Preference String.
          * "SIP_ALWAYS" : Always use SIP with network access
@@ -4395,29 +4125,18 @@
          */
         public static final String SIP_CALL_OPTIONS = "sip_call_options";
 
-        /** @hide */
-        public static final Validator SIP_CALL_OPTIONS_VALIDATOR =
-                new DiscreteValueValidator(
-                        new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
-
         /**
          * One of the sip call options: Always use SIP with network access.
          * @hide
          */
         public static final String SIP_ALWAYS = "SIP_ALWAYS";
 
-        /** @hide */
-        public static final Validator SIP_ALWAYS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * One of the sip call options: Only if destination is a SIP address.
          * @hide
          */
         public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
 
-        /** @hide */
-        public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use SIP_ALWAYS or SIP_ADDRESS_ONLY instead.  Formerly used to indicate that
          * the user should be prompted each time a call is made whether it should be placed using
@@ -4428,9 +4147,6 @@
         @Deprecated
         public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
 
-        /** @hide */
-        public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Pointer speed setting.
          * This is an integer value in a range between -7 and +7, so there are 15 possible values.
@@ -4442,19 +4158,12 @@
         @UnsupportedAppUsage
         public static final String POINTER_SPEED = "pointer_speed";
 
-        /** @hide */
-        public static final Validator POINTER_SPEED_VALIDATOR =
-                new InclusiveFloatRangeValidator(-7, 7);
-
         /**
          * Whether lock-to-app will be triggered by long-press on recents.
          * @hide
          */
         public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
 
-        /** @hide */
-        public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * I am the lolrus.
          * <p>
@@ -4464,18 +4173,6 @@
          */
         public static final String EGG_MODE = "egg_mode";
 
-        /** @hide */
-        public static final Validator EGG_MODE_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                try {
-                    return Long.parseLong(value) >= 0;
-                } catch (NumberFormatException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * Setting to determine whether or not to show the battery percentage in the status bar.
          *    0 - Don't show percentage
@@ -4484,9 +4181,6 @@
          */
         public static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent";
 
-        /** @hide */
-        private static final Validator SHOW_BATTERY_PERCENT_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * IMPORTANT: If you add a new public settings you also have to add it to
          * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
@@ -4495,73 +4189,6 @@
          */
 
         /**
-         * Settings to backup. This is here so that it's in the same place as the settings
-         * keys and easy to update.
-         *
-         * NOTE: Settings are backed up and restored in the order they appear
-         *       in this array. If you have one setting depending on another,
-         *       make sure that they are ordered appropriately.
-         *
-         * @hide
-         */
-        @UnsupportedAppUsage
-        public static final String[] SETTINGS_TO_BACKUP = {
-            STAY_ON_WHILE_PLUGGED_IN,   // moved to global
-            WIFI_USE_STATIC_IP,
-            WIFI_STATIC_IP,
-            WIFI_STATIC_GATEWAY,
-            WIFI_STATIC_NETMASK,
-            WIFI_STATIC_DNS1,
-            WIFI_STATIC_DNS2,
-            BLUETOOTH_DISCOVERABILITY,
-            BLUETOOTH_DISCOVERABILITY_TIMEOUT,
-            FONT_SCALE,
-            DIM_SCREEN,
-            SCREEN_OFF_TIMEOUT,
-            SCREEN_BRIGHTNESS_MODE,
-            SCREEN_AUTO_BRIGHTNESS_ADJ,
-            SCREEN_BRIGHTNESS_FOR_VR,
-            ADAPTIVE_SLEEP,
-            VIBRATE_INPUT_DEVICES,
-            MODE_RINGER_STREAMS_AFFECTED,
-            TEXT_AUTO_REPLACE,
-            TEXT_AUTO_CAPS,
-            TEXT_AUTO_PUNCTUATE,
-            TEXT_SHOW_PASSWORD,
-            AUTO_TIME,                  // moved to global
-            AUTO_TIME_ZONE,             // moved to global
-            TIME_12_24,
-            DATE_FORMAT,
-            DTMF_TONE_WHEN_DIALING,
-            DTMF_TONE_TYPE_WHEN_DIALING,
-            HEARING_AID,
-            TTY_MODE,
-            MASTER_MONO,
-            MASTER_BALANCE,
-            SOUND_EFFECTS_ENABLED,
-            HAPTIC_FEEDBACK_ENABLED,
-            POWER_SOUNDS_ENABLED,       // moved to global
-            DOCK_SOUNDS_ENABLED,        // moved to global
-            LOCKSCREEN_SOUNDS_ENABLED,
-            SHOW_WEB_SUGGESTIONS,
-            SIP_CALL_OPTIONS,
-            SIP_RECEIVE_CALLS,
-            POINTER_SPEED,
-            VIBRATE_WHEN_RINGING,
-            RINGTONE,
-            LOCK_TO_APP_ENABLED,
-            NOTIFICATION_SOUND,
-            ACCELEROMETER_ROTATION,
-            SHOW_BATTERY_PERCENT,
-            NOTIFICATION_VIBRATION_INTENSITY,
-            RING_VIBRATION_INTENSITY,
-            HAPTIC_FEEDBACK_INTENSITY,
-            DISPLAY_COLOR_MODE,
-            ALARM_ALERT,
-            NOTIFICATION_LIGHT_PULSE,
-        };
-
-        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -4681,99 +4308,6 @@
         }
 
         /**
-         * These are all public system settings
-         *
-         * All settings in {@link SETTINGS_TO_BACKUP} array *must* have a non-null validator,
-         * otherwise they won't be restored.
-         *
-         * @hide
-         */
-        @UnsupportedAppUsage
-        public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
-        static {
-            VALIDATORS.put(STAY_ON_WHILE_PLUGGED_IN, STAY_ON_WHILE_PLUGGED_IN_VALIDATOR);
-            VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
-            VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
-            VALIDATORS.put(BLUETOOTH_DISCOVERABILITY, BLUETOOTH_DISCOVERABILITY_VALIDATOR);
-            VALIDATORS.put(BLUETOOTH_DISCOVERABILITY_TIMEOUT,
-                    BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR);
-            VALIDATORS.put(NEXT_ALARM_FORMATTED, NEXT_ALARM_FORMATTED_VALIDATOR);
-            VALIDATORS.put(FONT_SCALE, FONT_SCALE_VALIDATOR);
-            VALIDATORS.put(DIM_SCREEN, DIM_SCREEN_VALIDATOR);
-            VALIDATORS.put(DISPLAY_COLOR_MODE, DISPLAY_COLOR_MODE_VALIDATOR);
-            VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
-            VALIDATORS.put(SCREEN_BRIGHTNESS_FOR_VR, SCREEN_BRIGHTNESS_FOR_VR_VALIDATOR);
-            VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
-            VALIDATORS.put(ADAPTIVE_SLEEP, ADAPTIVE_SLEEP_VALIDATOR);
-            VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
-            VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
-            VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
-            VALIDATORS.put(RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
-            VALIDATORS.put(HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
-            VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
-            VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);
-            VALIDATORS.put(TEXT_AUTO_REPLACE, TEXT_AUTO_REPLACE_VALIDATOR);
-            VALIDATORS.put(TEXT_AUTO_CAPS, TEXT_AUTO_CAPS_VALIDATOR);
-            VALIDATORS.put(TEXT_AUTO_PUNCTUATE, TEXT_AUTO_PUNCTUATE_VALIDATOR);
-            VALIDATORS.put(TEXT_SHOW_PASSWORD, TEXT_SHOW_PASSWORD_VALIDATOR);
-            VALIDATORS.put(AUTO_TIME, AUTO_TIME_VALIDATOR);
-            VALIDATORS.put(AUTO_TIME_ZONE, AUTO_TIME_ZONE_VALIDATOR);
-            VALIDATORS.put(SHOW_GTALK_SERVICE_STATUS, SHOW_GTALK_SERVICE_STATUS_VALIDATOR);
-            VALIDATORS.put(WALLPAPER_ACTIVITY, WALLPAPER_ACTIVITY_VALIDATOR);
-            VALIDATORS.put(TIME_12_24, TIME_12_24_VALIDATOR);
-            VALIDATORS.put(DATE_FORMAT, DATE_FORMAT_VALIDATOR);
-            VALIDATORS.put(SETUP_WIZARD_HAS_RUN, SETUP_WIZARD_HAS_RUN_VALIDATOR);
-            VALIDATORS.put(ACCELEROMETER_ROTATION, ACCELEROMETER_ROTATION_VALIDATOR);
-            VALIDATORS.put(USER_ROTATION, USER_ROTATION_VALIDATOR);
-            VALIDATORS.put(DTMF_TONE_WHEN_DIALING, DTMF_TONE_WHEN_DIALING_VALIDATOR);
-            VALIDATORS.put(SOUND_EFFECTS_ENABLED, SOUND_EFFECTS_ENABLED_VALIDATOR);
-            VALIDATORS.put(HAPTIC_FEEDBACK_ENABLED, HAPTIC_FEEDBACK_ENABLED_VALIDATOR);
-            VALIDATORS.put(POWER_SOUNDS_ENABLED, POWER_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(DOCK_SOUNDS_ENABLED, DOCK_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(SHOW_WEB_SUGGESTIONS, SHOW_WEB_SUGGESTIONS_VALIDATOR);
-            VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
-            VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
-            VALIDATORS.put(ADVANCED_SETTINGS, ADVANCED_SETTINGS_VALIDATOR);
-            VALIDATORS.put(SCREEN_AUTO_BRIGHTNESS_ADJ, SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR);
-            VALIDATORS.put(VIBRATE_INPUT_DEVICES, VIBRATE_INPUT_DEVICES_VALIDATOR);
-            VALIDATORS.put(MASTER_MONO, MASTER_MONO_VALIDATOR);
-            VALIDATORS.put(MASTER_BALANCE, MASTER_BALANCE_VALIDATOR);
-            VALIDATORS.put(NOTIFICATIONS_USE_RING_VOLUME, NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR);
-            VALIDATORS.put(VIBRATE_IN_SILENT, VIBRATE_IN_SILENT_VALIDATOR);
-            VALIDATORS.put(MEDIA_BUTTON_RECEIVER, MEDIA_BUTTON_RECEIVER_VALIDATOR);
-            VALIDATORS.put(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
-                    HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR);
-            VALIDATORS.put(VIBRATE_WHEN_RINGING, VIBRATE_WHEN_RINGING_VALIDATOR);
-            VALIDATORS.put(DTMF_TONE_TYPE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR);
-            VALIDATORS.put(HEARING_AID, HEARING_AID_VALIDATOR);
-            VALIDATORS.put(TTY_MODE, TTY_MODE_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, NOTIFICATION_LIGHT_PULSE_VALIDATOR);
-            VALIDATORS.put(POINTER_LOCATION, POINTER_LOCATION_VALIDATOR);
-            VALIDATORS.put(SHOW_TOUCHES, SHOW_TOUCHES_VALIDATOR);
-            VALIDATORS.put(WINDOW_ORIENTATION_LISTENER_LOG,
-                    WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR);
-            VALIDATORS.put(LOCKSCREEN_SOUNDS_ENABLED, LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(LOCKSCREEN_DISABLED, LOCKSCREEN_DISABLED_VALIDATOR);
-            VALIDATORS.put(SIP_RECEIVE_CALLS, SIP_RECEIVE_CALLS_VALIDATOR);
-            VALIDATORS.put(SIP_CALL_OPTIONS, SIP_CALL_OPTIONS_VALIDATOR);
-            VALIDATORS.put(SIP_ALWAYS, SIP_ALWAYS_VALIDATOR);
-            VALIDATORS.put(SIP_ADDRESS_ONLY, SIP_ADDRESS_ONLY_VALIDATOR);
-            VALIDATORS.put(SIP_ASK_ME_EACH_TIME, SIP_ASK_ME_EACH_TIME_VALIDATOR);
-            VALIDATORS.put(POINTER_SPEED, POINTER_SPEED_VALIDATOR);
-            VALIDATORS.put(LOCK_TO_APP_ENABLED, LOCK_TO_APP_ENABLED_VALIDATOR);
-            VALIDATORS.put(EGG_MODE, EGG_MODE_VALIDATOR);
-            VALIDATORS.put(WIFI_STATIC_IP, WIFI_STATIC_IP_VALIDATOR);
-            VALIDATORS.put(WIFI_STATIC_GATEWAY, WIFI_STATIC_GATEWAY_VALIDATOR);
-            VALIDATORS.put(WIFI_STATIC_NETMASK, WIFI_STATIC_NETMASK_VALIDATOR);
-            VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
-            VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
-            VALIDATORS.put(SHOW_BATTERY_PERCENT, SHOW_BATTERY_PERCENT_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
-        }
-
-        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
@@ -4859,8 +4393,6 @@
         @Deprecated
         public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
 
-        private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
          */
@@ -4938,8 +4470,6 @@
         @Deprecated
         public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
 
-        private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
          */
@@ -4969,9 +4499,6 @@
         public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
                 Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
 
-        private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use
          * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
@@ -4980,9 +4507,6 @@
         public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
                 Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
 
-        private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT}
          * instead
@@ -4990,9 +4514,6 @@
         @Deprecated
         public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Global.WIFI_NUM_OPEN_NETWORKS_KEPT;
 
-        private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} instead
          */
@@ -5757,8 +5278,6 @@
         @Deprecated
         public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
 
-        private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead
          */
@@ -5776,8 +5295,6 @@
         @Deprecated
         public static final String ALLOW_MOCK_LOCATION = "mock_location";
 
-        private static final Validator ALLOW_MOCK_LOCATION_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting to indicate that on device captions are enabled.
          *
@@ -5786,8 +5303,6 @@
         @SystemApi
         public static final String ODI_CAPTIONS_ENABLED = "odi_captions_enabled";
 
-        private static final Validator ODI_CAPTIONS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * On Android 8.0 (API level 26) and higher versions of the platform,
          * a 64-bit number (expressed as a hexadecimal string), unique to
@@ -5833,8 +5348,6 @@
         @Deprecated
         public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
 
-        private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
          */
@@ -5882,9 +5395,6 @@
         @TestApi
         public static final String AUTOFILL_SERVICE = "autofill_service";
 
-        private static final Validator AUTOFILL_SERVICE_VALIDATOR =
-                NULLABLE_COMPONENT_NAME_VALIDATOR;
-
         /**
          * Boolean indicating if Autofill supports field classification.
          *
@@ -6098,8 +5608,6 @@
          */
         public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
 
-        private static final Validator SHOW_IME_WITH_HARD_KEYBOARD_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Host name and port for global http proxy. Uses ':' seperator for
          * between host and port.
@@ -6367,9 +5875,6 @@
          */
         public static final String LOCK_SCREEN_CUSTOM_CLOCK_FACE = "lock_screen_custom_clock_face";
 
-        private static final Validator LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR =
-                JSON_OBJECT_VALIDATOR;
-
         /**
          * Indicates which clock face to show on lock screen and AOD while docked.
          * @hide
@@ -6435,8 +5940,6 @@
         @Deprecated
         public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
 
-        private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
          */
@@ -6448,8 +5951,6 @@
          */
         public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
 
-        private static final Validator ACCESSIBILITY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting specifying if the accessibility shortcut is enabled.
          * @hide
@@ -6457,8 +5958,6 @@
         public static final String ACCESSIBILITY_SHORTCUT_ENABLED =
                 "accessibility_shortcut_enabled";
 
-        private static final Validator ACCESSIBILITY_SHORTCUT_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting specifying if the accessibility shortcut is enabled.
          * @hide
@@ -6466,9 +5965,6 @@
         public static final String ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN =
                 "accessibility_shortcut_on_lock_screen";
 
-        private static final Validator ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting specifying if the accessibility shortcut dialog has been shown to this user.
          * @hide
@@ -6476,9 +5972,6 @@
         public static final String ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN =
                 "accessibility_shortcut_dialog_shown";
 
-        private static final Validator ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting specifying the accessibility service to be toggled via the accessibility
          * shortcut. Must be its flattened {@link ComponentName}.
@@ -6488,9 +5981,6 @@
         public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE =
                 "accessibility_shortcut_target_service";
 
-        private static final Validator ACCESSIBILITY_SHORTCUT_TARGET_SERVICE_VALIDATOR =
-                NULLABLE_COMPONENT_NAME_VALIDATOR;
-
         /**
          * Setting specifying the accessibility service or feature to be toggled via the
          * accessibility button in the navigation bar. This is either a flattened
@@ -6501,32 +5991,17 @@
         public static final String ACCESSIBILITY_BUTTON_TARGET_COMPONENT =
                 "accessibility_button_target_component";
 
-        private static final Validator ACCESSIBILITY_BUTTON_TARGET_COMPONENT_VALIDATOR =
-                new Validator() {
-                    @Override
-                    public boolean validate(@Nullable String value) {
-                        // technically either ComponentName or class name, but there's proper value
-                        // validation at callsites, so allow any non-null string
-                        return value != null;
-                    }
-                };
-
         /**
          * If touch exploration is enabled.
          */
         public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
 
-        private static final Validator TOUCH_EXPLORATION_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * List of the enabled accessibility providers.
          */
         public static final String ENABLED_ACCESSIBILITY_SERVICES =
             "enabled_accessibility_services";
 
-        private static final Validator ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR =
-                new ComponentNameListValidator(":");
-
         /**
          * List of the accessibility services to which the user has granted
          * permission to put the device into touch exploration mode.
@@ -6536,17 +6011,12 @@
         public static final String TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES =
             "touch_exploration_granted_accessibility_services";
 
-        private static final Validator TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR =
-                new ComponentNameListValidator(":");
-
         /**
          * Whether the Global Actions Panel is enabled.
          * @hide
          */
         public static final String GLOBAL_ACTIONS_PANEL_ENABLED = "global_actions_panel_enabled";
 
-        private static final Validator GLOBAL_ACTIONS_PANEL_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the Global Actions Panel can be toggled on or off in Settings.
          * @hide
@@ -6568,17 +6038,12 @@
         @SystemApi
         public static final String HUSH_GESTURE_USED = "hush_gesture_used";
 
-        private static final Validator HUSH_GESTURE_USED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Number of times the user has manually clicked the ringer toggle
          * @hide
          */
         public static final String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count";
 
-        private static final Validator MANUAL_RINGER_TOGGLE_COUNT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Whether to play a sound for charging events.
          * @hide
@@ -6600,8 +6065,6 @@
          */
         public static final String ZEN_DURATION = "zen_duration";
 
-        private static final Validator ZEN_DURATION_VALIDATOR = ANY_INTEGER_VALIDATOR;
-
         /** @hide */ public static final int ZEN_DURATION_PROMPT = -1;
         /** @hide */ public static final int ZEN_DURATION_FOREVER = 0;
 
@@ -6637,8 +6100,6 @@
          */
         public static final String IN_CALL_NOTIFICATION_ENABLED = "in_call_notification_enabled";
 
-        private static final Validator IN_CALL_NOTIFICATION_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Uri of the slice that's presented on the keyguard.
          * Defaults to a slice with the date and next alarm.
@@ -6665,9 +6126,6 @@
         public static final String ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED =
                 "high_text_contrast_enabled";
 
-        private static final Validator ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting that specifies whether the display magnification is enabled via a system-wide
          * triple tap gesture. Display magnifications allows the user to zoom in the display content
@@ -6680,9 +6138,6 @@
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
                 "accessibility_display_magnification_enabled";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting that specifies whether the display magnification is enabled via a shortcut
          * affordance within the system's navigation area. Display magnifications allows the user to
@@ -6695,9 +6150,6 @@
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED =
                 "accessibility_display_magnification_navbar_enabled";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED_VALIDATOR
-                = BOOLEAN_VALIDATOR;
-
         /**
          * Setting that specifies what the display magnification scale is.
          * Display magnifications allows the user to zoom in the display
@@ -6711,9 +6163,6 @@
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE =
                 "accessibility_display_magnification_scale";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR =
-                new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE);
-
         /**
          * Unused mangnification setting
          *
@@ -6766,9 +6215,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_ENABLED =
                 "accessibility_captioning_enabled";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting that specifies the language for captions as a locale string,
          * e.g. en_US.
@@ -6779,8 +6225,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_LOCALE =
                 "accessibility_captioning_locale";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_LOCALE_VALIDATOR = LOCALE_VALIDATOR;
-
         /**
          * Integer property that specifies the preset style for captions, one
          * of:
@@ -6795,10 +6239,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_PRESET =
                 "accessibility_captioning_preset";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"-1", "0", "1", "2",
-                        "3", "4"});
-
         /**
          * Integer property that specifes the background color for captions as a
          * packed 32-bit color.
@@ -6809,9 +6249,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR =
                 "accessibility_captioning_background_color";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR_VALIDATOR =
-                ANY_INTEGER_VALIDATOR;
-
         /**
          * Integer property that specifes the foreground color for captions as a
          * packed 32-bit color.
@@ -6822,9 +6259,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR =
                 "accessibility_captioning_foreground_color";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR_VALIDATOR =
-                ANY_INTEGER_VALIDATOR;
-
         /**
          * Integer property that specifes the edge type for captions, one of:
          * <ul>
@@ -6839,9 +6273,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_EDGE_TYPE =
                 "accessibility_captioning_edge_type";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"0", "1", "2"});
-
         /**
          * Integer property that specifes the edge color for captions as a
          * packed 32-bit color.
@@ -6853,9 +6284,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_EDGE_COLOR =
                 "accessibility_captioning_edge_color";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_EDGE_COLOR_VALIDATOR =
-                ANY_INTEGER_VALIDATOR;
-
         /**
          * Integer property that specifes the window color for captions as a
          * packed 32-bit color.
@@ -6866,9 +6294,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_WINDOW_COLOR =
                 "accessibility_captioning_window_color";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_WINDOW_COLOR_VALIDATOR =
-                ANY_INTEGER_VALIDATOR;
-
         /**
          * String property that specifies the typeface for captions, one of:
          * <ul>
@@ -6885,10 +6310,6 @@
         public static final String ACCESSIBILITY_CAPTIONING_TYPEFACE =
                 "accessibility_captioning_typeface";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"DEFAULT",
-                        "MONOSPACE", "SANS_SERIF", "SERIF"});
-
         /**
          * Floating point property that specifies font scaling for captions.
          *
@@ -6897,18 +6318,12 @@
         public static final String ACCESSIBILITY_CAPTIONING_FONT_SCALE =
                 "accessibility_captioning_font_scale";
 
-        private static final Validator ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR =
-                new InclusiveFloatRangeValidator(0.5f, 2.0f);
-
         /**
          * Setting that specifies whether display color inversion is enabled.
          */
         public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED =
                 "accessibility_display_inversion_enabled";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_INVERSION_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting that specifies whether display color space adjustment is
          * enabled.
@@ -6919,9 +6334,6 @@
         public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED =
                 "accessibility_display_daltonizer_enabled";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Integer property that specifies the type of color space adjustment to
          * perform. Valid values are defined in AccessibilityManager and Settings arrays.xml:
@@ -6938,10 +6350,6 @@
         public static final String ACCESSIBILITY_DISPLAY_DALTONIZER =
                 "accessibility_display_daltonizer";
 
-        private static final Validator ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR =
-                new DiscreteValueValidator(
-                        new String[] {"-1", "0", "11", "12", "13"});
-
         /**
          * Setting that specifies whether automatic click when the mouse pointer stops moving is
          * enabled.
@@ -6952,9 +6360,6 @@
         public static final String ACCESSIBILITY_AUTOCLICK_ENABLED =
                 "accessibility_autoclick_enabled";
 
-        private static final Validator ACCESSIBILITY_AUTOCLICK_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Integer setting specifying amount of time in ms the mouse pointer has to stay still
          * before performing click when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
@@ -6965,9 +6370,6 @@
         public static final String ACCESSIBILITY_AUTOCLICK_DELAY =
                 "accessibility_autoclick_delay";
 
-        private static final Validator ACCESSIBILITY_AUTOCLICK_DELAY_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Whether or not larger size icons are used for the pointer of mouse/trackpad for
          * accessibility.
@@ -6978,9 +6380,6 @@
         public static final String ACCESSIBILITY_LARGE_POINTER_ICON =
                 "accessibility_large_pointer_icon";
 
-        private static final Validator ACCESSIBILITY_LARGE_POINTER_ICON_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * The timeout for considering a press to be a long press in milliseconds.
          * @hide
@@ -6988,9 +6387,6 @@
         @UnsupportedAppUsage
         public static final String LONG_PRESS_TIMEOUT = "long_press_timeout";
 
-        private static final Validator LONG_PRESS_TIMEOUT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * The duration in milliseconds between the first tap's up event and the second tap's
          * down event for an interaction to be considered part of the same multi-press.
@@ -7046,8 +6442,6 @@
          */
         public static final String DISPLAY_DENSITY_FORCED = "display_density_forced";
 
-        static final Validator DISPLAY_DENSITY_FORCED_VALIDATOR = NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Setting to always use the default text-to-speech settings regardless
          * of the application settings.
@@ -7065,22 +6459,16 @@
          */
         public static final String TTS_DEFAULT_RATE = "tts_default_rate";
 
-        private static final Validator TTS_DEFAULT_RATE_VALIDATOR = NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Default text-to-speech engine pitch. 100 = 1x
          */
         public static final String TTS_DEFAULT_PITCH = "tts_default_pitch";
 
-        private static final Validator TTS_DEFAULT_PITCH_VALIDATOR = NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Default text-to-speech engine.
          */
         public static final String TTS_DEFAULT_SYNTH = "tts_default_synth";
 
-        private static final Validator TTS_DEFAULT_SYNTH_VALIDATOR = PACKAGE_NAME_VALIDATOR;
-
         /**
          * Default text-to-speech language.
          *
@@ -7128,16 +6516,11 @@
          */
         public static final String TTS_DEFAULT_LOCALE = "tts_default_locale";
 
-        private static final Validator TTS_DEFAULT_LOCALE_VALIDATOR = TTS_LIST_VALIDATOR;
-
         /**
          * Space delimited list of plugin packages that are enabled.
          */
         public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins";
 
-        private static final Validator TTS_ENABLED_PLUGINS_VALIDATOR =
-                new PackageNameListValidator(" ");
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON}
          * instead.
@@ -7146,9 +6529,6 @@
         public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
                 Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
 
-        private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY}
          * instead.
@@ -7157,9 +6537,6 @@
         public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
                 Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
 
-        private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT}
          * instead.
@@ -7168,9 +6545,6 @@
         public static final String WIFI_NUM_OPEN_NETWORKS_KEPT =
                 Global.WIFI_NUM_OPEN_NETWORKS_KEPT;
 
-        private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON}
          * instead.
@@ -7329,9 +6703,6 @@
         public static final String PREFERRED_TTY_MODE =
                 "preferred_tty_mode";
 
-        private static final Validator PREFERRED_TTY_MODE_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"0", "1", "2", "3"});
-
         /**
          * Whether the enhanced voice privacy mode is enabled.
          * 0 = normal voice privacy
@@ -7340,8 +6711,6 @@
          */
         public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled";
 
-        private static final Validator ENHANCED_VOICE_PRIVACY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the TTY mode mode is enabled.
          * 0 = disabled
@@ -7350,8 +6719,6 @@
          */
         public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
 
-        private static final Validator TTY_MODE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * User-selected RTT mode. When on, outgoing and incoming calls will be answered as RTT
          * calls when supported by the device and carrier. Boolean value.
@@ -7360,8 +6727,6 @@
          */
         public static final String RTT_CALLING_MODE = "rtt_calling_mode";
 
-        private static final Validator RTT_CALLING_MODE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
         /**
          * Controls whether settings backup is enabled.
@@ -7539,32 +6904,24 @@
          */
         public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd";
 
-        private static final Validator MOUNT_PLAY_NOTIFICATION_SND_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true)
          * @hide
          */
         public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart";
 
-        private static final Validator MOUNT_UMS_AUTOSTART_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true)
          * @hide
          */
         public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt";
 
-        private static final Validator MOUNT_UMS_PROMPT_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true)
          * @hide
          */
         public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
 
-        private static final Validator MOUNT_UMS_NOTIFY_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * If nonzero, ANRs in invisible background processes bring up a dialog.
          * Otherwise, the process will be silently killed.
@@ -7583,9 +6940,6 @@
         public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION =
                 "show_first_crash_dialog_dev_option";
 
-        private static final Validator SHOW_FIRST_CRASH_DIALOG_DEV_OPTION_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * The {@link ComponentName} string of the service to be used as the voice recognition
          * service.
@@ -7644,9 +6998,6 @@
         @UnsupportedAppUsage
         public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
 
-        private static final Validator INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"1", "2"});
-
         /**
          * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
          * @hide
@@ -7702,8 +7053,6 @@
          */
         public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled";
 
-        private static final Validator WAKE_GESTURE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the device should doze if configured.
          * @hide
@@ -7711,8 +7060,6 @@
         @UnsupportedAppUsage
         public static final String DOZE_ENABLED = "doze_enabled";
 
-        private static final Validator DOZE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Indicates whether doze should be always on.
          * <p>
@@ -7724,16 +7071,12 @@
         @TestApi
         public static final String DOZE_ALWAYS_ON = "doze_always_on";
 
-        private static final Validator DOZE_ALWAYS_ON_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the device should pulse on pick up gesture.
          * @hide
          */
         public static final String DOZE_PICK_UP_GESTURE = "doze_pulse_on_pick_up";
 
-        private static final Validator DOZE_PICK_UP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the device should pulse on long press gesture.
          * @hide
@@ -7746,24 +7089,18 @@
          */
         public static final String DOZE_DOUBLE_TAP_GESTURE = "doze_pulse_on_double_tap";
 
-        private static final Validator DOZE_DOUBLE_TAP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the device should respond to the SLPI tap gesture.
          * @hide
          */
         public static final String DOZE_TAP_SCREEN_GESTURE = "doze_tap_gesture";
 
-        private static final Validator DOZE_TAP_SCREEN_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Gesture that wakes up the display, showing some version of the lock screen.
          * @hide
          */
         public static final String DOZE_WAKE_LOCK_SCREEN_GESTURE = "doze_wake_screen_gesture";
 
-        private static final Validator DOZE_WAKE_LOCK_SCREEN_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Gesture that wakes up the display, toggling between {@link Display.STATE_OFF} and
          * {@link Display.STATE_DOZE}.
@@ -7771,16 +7108,12 @@
          */
         public static final String DOZE_WAKE_DISPLAY_GESTURE = "doze_wake_display_gesture";
 
-        private static final Validator DOZE_WAKE_DISPLAY_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Gesture that skips media.
          * @hide
          */
         public static final String SKIP_GESTURE = "skip_gesture";
 
-        private static final Validator SKIP_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Count of successful gestures.
          * @hide
@@ -7793,9 +7126,6 @@
          */
         public static final String SKIP_TOUCH_COUNT = "skip_touch_count";
 
-        private static final Validator SKIP_GESTURE_COUNT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Direction to advance media for skip gesture
          * @hide
@@ -7803,21 +7133,11 @@
         public static final String SKIP_DIRECTION = "skip_gesture_direction";
 
         /**
-         * Only used if FeatureFlag "settings_skip_direction_mutable" is enabled.
-         * If feature flag is disabled, should assume SKIP_DIRECTION = 0.
-         *      0 / false = right to left to advance to next
-         *      1 / true = left to right to advance to next
-         */
-        private static final Validator SKIP_DIRECTION_VALIDATOR = BOOLEAN_VALIDATOR;
-
-        /**
          * Gesture that silences sound (alarms, notification, calls).
          * @hide
          */
         public static final String SILENCE_GESTURE = "silence_gesture";
 
-        private static final Validator SILENCE_GESTURE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Count of successful silence alarms gestures.
          * @hide
@@ -7854,9 +7174,6 @@
          */
         public static final String SILENCE_CALL_TOUCH_COUNT = "silence_call_touch_count";
 
-        private static final Validator SILENCE_GESTURE_COUNT_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * The current night mode that has been selected by the user.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
@@ -7865,17 +7182,12 @@
          */
         public static final String UI_NIGHT_MODE = "ui_night_mode";
 
-        private static final Validator UI_NIGHT_MODE_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 2);
-
         /**
          * Whether screensavers are enabled.
          * @hide
          */
         public static final String SCREENSAVER_ENABLED = "screensaver_enabled";
 
-        private static final Validator SCREENSAVER_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The user's chosen screensaver components.
          *
@@ -7885,9 +7197,6 @@
          */
         public static final String SCREENSAVER_COMPONENTS = "screensaver_components";
 
-        private static final Validator SCREENSAVER_COMPONENTS_VALIDATOR =
-                new ComponentNameListValidator(",");
-
         /**
          * If screensavers are enabled, whether the screensaver should be automatically launched
          * when the device is inserted into a (desk) dock.
@@ -7895,8 +7204,6 @@
          */
         public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock";
 
-        private static final Validator SCREENSAVER_ACTIVATE_ON_DOCK_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * If screensavers are enabled, whether the screensaver should be automatically launched
          * when the screen times out when not on battery.
@@ -7904,8 +7211,6 @@
          */
         public static final String SCREENSAVER_ACTIVATE_ON_SLEEP = "screensaver_activate_on_sleep";
 
-        private static final Validator SCREENSAVER_ACTIVATE_ON_SLEEP_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * If screensavers are enabled, the default screensaver component.
          * @hide
@@ -7919,9 +7224,6 @@
         @UnsupportedAppUsage
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
-        private static final Validator NFC_PAYMENT_DEFAULT_COMPONENT_VALIDATOR =
-                COMPONENT_NAME_VALIDATOR;
-
         /**
          * Whether NFC payment is handled by the foreground application or a default.
          * @hide
@@ -8037,9 +7339,6 @@
         public static final String ENABLED_NOTIFICATION_ASSISTANT =
                 "enabled_notification_assistant";
 
-        private static final Validator ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR =
-                new ComponentNameListValidator(":");
-
         /**
          * Read only list of the service components that the current user has explicitly allowed to
          * see all of the user's notifications, separated by ':'.
@@ -8052,9 +7351,6 @@
         @UnsupportedAppUsage
         public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
 
-        private static final Validator ENABLED_NOTIFICATION_LISTENERS_VALIDATOR =
-                new ComponentNameListValidator(":");
-
         /**
          * Read only list of the packages that the current user has explicitly allowed to
          * manage do not disturb, separated by ':'.
@@ -8067,9 +7363,6 @@
         public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
                 "enabled_notification_policy_access_packages";
 
-        private static final Validator ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR =
-                new PackageNameListValidator(":");
-
         /**
          * Defines whether managed profile ringtones should be synced from it's parent profile
          * <p>
@@ -8083,8 +7376,6 @@
         @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
         public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
 
-        private static final Validator SYNC_PARENT_SOUNDS_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /** @hide */
         @UnsupportedAppUsage
         public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
@@ -8218,8 +7509,6 @@
          */
         public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake";
 
-        private static final Validator DOUBLE_TAP_TO_WAKE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The current assistant component. It could be a voice interaction service,
          * or an activity that handles ACTION_ASSIST, or empty which means using the default
@@ -8240,8 +7529,6 @@
          */
         public static final String CAMERA_GESTURE_DISABLED = "camera_gesture_disabled";
 
-        private static final Validator CAMERA_GESTURE_DISABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
@@ -8251,9 +7538,6 @@
         public static final String CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED =
                 "camera_double_tap_power_gesture_disabled";
 
-        private static final Validator CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether the camera double twist gesture to flip between front and back mode should be
          * enabled.
@@ -8263,9 +7547,6 @@
         public static final String CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED =
                 "camera_double_twist_to_flip_enabled";
 
-        private static final Validator CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not the smart camera lift trigger that launches the camera when the user moves
          * the phone into a position for taking photos should be enabled.
@@ -8302,9 +7583,6 @@
          */
         public static final String FACE_UNLOCK_KEYGUARD_ENABLED = "face_unlock_keyguard_enabled";
 
-        private static final Validator FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not face unlock dismisses the keyguard.
          * @hide
@@ -8312,9 +7590,6 @@
         public static final String FACE_UNLOCK_DISMISSES_KEYGUARD =
                 "face_unlock_dismisses_keyguard";
 
-        private static final Validator FACE_UNLOCK_DISMISSES_KEYGUARD_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not media is shown automatically when bypassing as a heads up.
          * @hide
@@ -8322,9 +7597,6 @@
         public static final String SHOW_MEDIA_WHEN_BYPASSING =
                 "show_media_when_bypassing";
 
-        private static final Validator SHOW_MEDIA_WHEN_BYPASSING_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not face unlock requires attention. This is a cached value, the source of
          * truth is obtained through the HAL.
@@ -8348,9 +7620,6 @@
          */
         public static final String FACE_UNLOCK_APP_ENABLED = "face_unlock_app_enabled";
 
-        private static final Validator FACE_UNLOCK_APP_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not face unlock always requires user confirmation, meaning {@link
          * android.hardware.biometrics.BiometricPrompt.Builder#setConfirmationRequired(boolean)}
@@ -8361,9 +7630,6 @@
         public static final String FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
                 "face_unlock_always_require_confirmation";
 
-        private static final Validator FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether or not debugging is enabled.
          * @hide
@@ -8378,9 +7644,6 @@
          */
         public static final String ASSIST_GESTURE_ENABLED = "assist_gesture_enabled";
 
-        private static final Validator ASSIST_GESTURE_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Sensitivity control for the assist gesture.
          *
@@ -8396,9 +7659,6 @@
         public static final String ASSIST_GESTURE_SILENCE_ALERTS_ENABLED =
                 "assist_gesture_silence_alerts_enabled";
 
-        private static final Validator ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Whether the assist gesture should wake the phone.
          *
@@ -8407,9 +7667,6 @@
         public static final String ASSIST_GESTURE_WAKE_ENABLED =
                 "assist_gesture_wake_enabled";
 
-        private static final Validator ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Indicates whether the Assist Gesture Deferred Setup has been completed.
          * <p>
@@ -8426,18 +7683,12 @@
          */
         public static final String TRUST_AGENTS_EXTEND_UNLOCK = "trust_agents_extend_unlock";
 
-        private static final Validator TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Control whether the screen locks when trust is lost.
          * @hide
          */
         public static final String LOCK_SCREEN_WHEN_TRUST_LOST = "lock_screen_when_trust_lost";
 
-        private static final Validator LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Control whether Night display is currently activated.
          * @hide
@@ -8450,9 +7701,6 @@
          */
         public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
 
-        private static final Validator NIGHT_DISPLAY_AUTO_MODE_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 2);
-
         /**
          * Control the color temperature of Night Display, represented in Kelvin.
          * @hide
@@ -8460,9 +7708,6 @@
         public static final String NIGHT_DISPLAY_COLOR_TEMPERATURE =
                 "night_display_color_temperature";
 
-        private static final Validator NIGHT_DISPLAY_COLOR_TEMPERATURE_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Custom time when Night display is scheduled to activate.
          * Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
@@ -8471,9 +7716,6 @@
         public static final String NIGHT_DISPLAY_CUSTOM_START_TIME =
                 "night_display_custom_start_time";
 
-        private static final Validator NIGHT_DISPLAY_CUSTOM_START_TIME_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Custom time when Night display is scheduled to deactivate.
          * Represented as milliseconds from midnight (e.g. 21600000 == 6am).
@@ -8481,9 +7723,6 @@
          */
         public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time";
 
-        private static final Validator NIGHT_DISPLAY_CUSTOM_END_TIME_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * A String representing the LocalDateTime when Night display was last activated. Use to
          * decide whether to apply the current activated state after a reboot or user change. In
@@ -8499,9 +7738,6 @@
          */
         public static final String DISPLAY_WHITE_BALANCE_ENABLED = "display_white_balance_enabled";
 
-        private static final Validator DISPLAY_WHITE_BALANCE_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Names of the service components that the current user has explicitly allowed to
          * be a VR mode listener, separated by ':'.
@@ -8511,9 +7747,6 @@
         @TestApi
         public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
 
-        private static final Validator ENABLED_VR_LISTENERS_VALIDATOR =
-                new ComponentNameListValidator(":");
-
         /**
          * Behavior of the display while in VR mode.
          *
@@ -8523,9 +7756,6 @@
          */
         public static final String VR_DISPLAY_MODE = "vr_display_mode";
 
-        private static final Validator VR_DISPLAY_MODE_VALIDATOR =
-                new DiscreteValueValidator(new String[]{"0", "1"});
-
         /**
          * Lower the display persistence while the system is in VR mode.
          *
@@ -8590,9 +7820,6 @@
         public static final String AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN =
                 "automatic_storage_manager_days_to_retain";
 
-        private static final Validator AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * Default number of days of information for the automatic storage manager to retain.
          *
@@ -8608,7 +7835,6 @@
         public static final String AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED =
                 "automatic_storage_manager_bytes_cleared";
 
-
         /**
          * Last run time for the automatic storage manager.
          *
@@ -8616,7 +7842,6 @@
          */
         public static final String AUTOMATIC_STORAGE_MANAGER_LAST_RUN =
                 "automatic_storage_manager_last_run";
-
         /**
          * If the automatic storage manager has been disabled by policy. Note that this doesn't
          * mean that the automatic storage manager is prevented from being re-enabled -- this only
@@ -8634,8 +7859,6 @@
         public static final String SYSTEM_NAVIGATION_KEYS_ENABLED =
                 "system_navigation_keys_enabled";
 
-        private static final Validator SYSTEM_NAVIGATION_KEYS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Holds comma separated list of ordering of QS tiles.
          *
@@ -8643,8 +7866,6 @@
          */
         public static final String QS_TILES = "sysui_qs_tiles";
 
-        private static final Validator QS_TILES_VALIDATOR = TILE_LIST_VALIDATOR;
-
         /**
          * Specifies whether the web action API is enabled.
          *
@@ -8660,14 +7881,6 @@
         public static final String DEVICE_PAIRED = "device_paired";
 
         /**
-         * Integer state indicating whether package verifier is enabled.
-         * TODO(b/34259924): Remove this setting.
-         *
-         * @hide
-         */
-        public static final String PACKAGE_VERIFIER_STATE = "package_verifier_state";
-
-        /**
          * Specifies additional package name for broadcasting the CMAS messages.
          * @hide
          */
@@ -8681,18 +7894,16 @@
         @TestApi
         public static final String NOTIFICATION_BADGING = "notification_badging";
 
-        private static final Validator NOTIFICATION_BADGING_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether the notification bubbles are globally enabled
          * The value is boolean (1 or 0).
          * @hide
+         * @deprecated use {@link Global#NOTIFICATION_BUBBLES} instead.
          */
         @TestApi
+        @Deprecated
         public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
 
-        private static final Validator NOTIFICATION_BUBBLES_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
          * swipe).
@@ -8701,24 +7912,18 @@
          */
         public static final String NOTIFICATION_DISMISS_RTL = "notification_dismiss_rtl";
 
-        private static final Validator NOTIFICATION_DISMISS_RTL_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Comma separated list of QS tiles that have been auto-added already.
          * @hide
          */
         public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
 
-        private static final Validator QS_AUTO_ADDED_TILES_VALIDATOR = TILE_LIST_VALIDATOR;
-
         /**
          * Whether the Lockdown button should be shown in the power menu.
          * @hide
          */
         public static final String LOCKDOWN_IN_POWER_MENU = "lockdown_in_power_menu";
 
-        private static final Validator LOCKDOWN_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Backup manager behavioral parameters.
          * This is encoded as a key=value list, separated by commas. Ex:
@@ -8790,9 +7995,6 @@
         @SystemApi
         public static final int VOLUME_HUSH_MUTE = 2;
 
-        private static final Validator VOLUME_HUSH_GESTURE_VALIDATOR =
-                NON_NEGATIVE_INTEGER_VALIDATOR;
-
         /**
          * The number of times (integer) the user has manually enabled battery saver.
          * @hide
@@ -8869,9 +8071,6 @@
         public static final String THEME_CUSTOMIZATION_OVERLAY_PACKAGES =
                 "theme_customization_overlay_packages";
 
-        private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
-                JSON_OBJECT_VALIDATOR;
-
         /**
          * Navigation bar mode.
          *  0 = 3 button
@@ -8881,8 +8080,6 @@
          */
         public static final String NAVIGATION_MODE =
                 "navigation_mode";
-        private static final Validator NAVIGATION_MODE_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1", "2"});
 
         /**
          * Controls whether aware is enabled.
@@ -8890,162 +8087,12 @@
          */
         public static final String AWARE_ENABLED = "aware_enabled";
 
-        private static final Validator AWARE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Controls whether aware_lock is enabled.
          * @hide
          */
         public static final String AWARE_LOCK_ENABLED = "aware_lock_enabled";
 
-        private static final Validator AWARE_LOCK_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
-        /**
-         * This are the settings to be backed up.
-         *
-         * NOTE: Settings are backed up and restored in the order they appear
-         *       in this array. If you have one setting depending on another,
-         *       make sure that they are ordered appropriately.
-         *
-         * @hide
-         */
-        @UnsupportedAppUsage
-        public static final String[] SETTINGS_TO_BACKUP = {
-            BUGREPORT_IN_POWER_MENU,                            // moved to global
-            ALLOW_MOCK_LOCATION,
-            USB_MASS_STORAGE_ENABLED,                           // moved to global
-            ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-            ACCESSIBILITY_DISPLAY_DALTONIZER,
-            ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-            ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
-            ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-            AUTOFILL_SERVICE,
-            ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-            ENABLED_ACCESSIBILITY_SERVICES,
-            ENABLED_VR_LISTENERS,
-            TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-            TOUCH_EXPLORATION_ENABLED,
-            ACCESSIBILITY_ENABLED,
-            ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-            ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
-            ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-            ACCESSIBILITY_SHORTCUT_ENABLED,
-            ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
-            ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
-            ACCESSIBILITY_CAPTIONING_PRESET,
-            ACCESSIBILITY_CAPTIONING_ENABLED,
-            ACCESSIBILITY_CAPTIONING_LOCALE,
-            ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
-            ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
-            ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
-            ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
-            ACCESSIBILITY_CAPTIONING_TYPEFACE,
-            ACCESSIBILITY_CAPTIONING_FONT_SCALE,
-            ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
-            TTS_DEFAULT_RATE,
-            TTS_DEFAULT_PITCH,
-            TTS_DEFAULT_SYNTH,
-            TTS_ENABLED_PLUGINS,
-            TTS_DEFAULT_LOCALE,
-            SHOW_IME_WITH_HARD_KEYBOARD,
-            WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,            // moved to global
-            WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,               // moved to global
-            WIFI_NUM_OPEN_NETWORKS_KEPT,                        // moved to global
-            MOUNT_PLAY_NOTIFICATION_SND,
-            MOUNT_UMS_AUTOSTART,
-            MOUNT_UMS_PROMPT,
-            MOUNT_UMS_NOTIFY_ENABLED,
-            DOUBLE_TAP_TO_WAKE,
-            WAKE_GESTURE_ENABLED,
-            LONG_PRESS_TIMEOUT,
-            CAMERA_GESTURE_DISABLED,
-            ACCESSIBILITY_AUTOCLICK_ENABLED,
-            ACCESSIBILITY_AUTOCLICK_DELAY,
-            ACCESSIBILITY_LARGE_POINTER_ICON,
-            PREFERRED_TTY_MODE,
-            ENHANCED_VOICE_PRIVACY_ENABLED,
-            TTY_MODE_ENABLED,
-            RTT_CALLING_MODE,
-            INCALL_POWER_BUTTON_BEHAVIOR,
-            NIGHT_DISPLAY_CUSTOM_START_TIME,
-            NIGHT_DISPLAY_CUSTOM_END_TIME,
-            NIGHT_DISPLAY_COLOR_TEMPERATURE,
-            NIGHT_DISPLAY_AUTO_MODE,
-            DISPLAY_WHITE_BALANCE_ENABLED,
-            SYNC_PARENT_SOUNDS,
-            CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
-            CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
-            SYSTEM_NAVIGATION_KEYS_ENABLED,
-            QS_TILES,
-            DOZE_ENABLED,
-            DOZE_ALWAYS_ON,
-            DOZE_PICK_UP_GESTURE,
-            DOZE_DOUBLE_TAP_GESTURE,
-            DOZE_TAP_SCREEN_GESTURE,
-            DOZE_WAKE_LOCK_SCREEN_GESTURE,
-            DOZE_WAKE_DISPLAY_GESTURE,
-            NFC_PAYMENT_DEFAULT_COMPONENT,
-            AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
-            FACE_UNLOCK_KEYGUARD_ENABLED,
-            SHOW_MEDIA_WHEN_BYPASSING,
-            FACE_UNLOCK_DISMISSES_KEYGUARD,
-            FACE_UNLOCK_APP_ENABLED,
-            FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
-            ASSIST_GESTURE_ENABLED,
-            ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
-            ASSIST_GESTURE_WAKE_ENABLED,
-            VR_DISPLAY_MODE,
-            NOTIFICATION_BADGING,
-            NOTIFICATION_BUBBLES,
-            NOTIFICATION_DISMISS_RTL,
-            QS_AUTO_ADDED_TILES,
-            SCREENSAVER_ENABLED,
-            SCREENSAVER_COMPONENTS,
-            SCREENSAVER_ACTIVATE_ON_DOCK,
-            SCREENSAVER_ACTIVATE_ON_SLEEP,
-            LOCKDOWN_IN_POWER_MENU,
-            SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
-            VOLUME_HUSH_GESTURE,
-            MANUAL_RINGER_TOGGLE_COUNT,
-            HUSH_GESTURE_USED,
-            IN_CALL_NOTIFICATION_ENABLED,
-            LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
-            LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-            LOCK_SCREEN_SHOW_NOTIFICATIONS,
-            LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
-            SHOW_NOTIFICATION_SNOOZE,
-            ZEN_DURATION,
-            SHOW_ZEN_UPGRADE_NOTIFICATION,
-            SHOW_ZEN_SETTINGS_SUGGESTION,
-            ZEN_SETTINGS_UPDATED,
-            ZEN_SETTINGS_SUGGESTION_VIEWED,
-            CHARGING_SOUNDS_ENABLED,
-            CHARGING_VIBRATION_ENABLED,
-            ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
-            ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
-            NOTIFICATION_NEW_INTERRUPTION_MODEL,
-            TRUST_AGENTS_EXTEND_UNLOCK,
-            UI_NIGHT_MODE,
-            LOCK_SCREEN_WHEN_TRUST_LOST,
-            SKIP_GESTURE,
-            SKIP_DIRECTION,
-            SILENCE_GESTURE,
-            THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
-            NAVIGATION_MODE,
-            AWARE_ENABLED,
-            SKIP_GESTURE_COUNT,
-            SKIP_TOUCH_COUNT,
-            SILENCE_ALARMS_GESTURE_COUNT,
-            SILENCE_CALL_GESTURE_COUNT,
-            SILENCE_TIMER_GESTURE_COUNT,
-            SILENCE_ALARMS_TOUCH_COUNT,
-            SILENCE_CALL_TOUCH_COUNT,
-            SILENCE_TIMER_TOUCH_COUNT,
-            DARK_MODE_DIALOG_SEEN,
-            GLOBAL_ACTIONS_PANEL_ENABLED,
-            AWARE_LOCK_ENABLED
-        };
-
         /**
          * The settings values which should only be restored if the target device is the
          * same as the source device
@@ -9061,201 +8108,6 @@
         };
 
         /**
-         * All settings in {@link SETTINGS_TO_BACKUP} and {@link DEVICE_SPECIFIC_SETTINGS_TO_BACKUP}
-         * array *must* have a non-null validator, otherwise they won't be restored.
-         *
-         * @hide
-         */
-        public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
-        static {
-            VALIDATORS.put(BUGREPORT_IN_POWER_MENU, BUGREPORT_IN_POWER_MENU_VALIDATOR);
-            VALIDATORS.put(ALLOW_MOCK_LOCATION, ALLOW_MOCK_LOCATION_VALIDATOR);
-            VALIDATORS.put(USB_MASS_STORAGE_ENABLED, USB_MASS_STORAGE_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
-                    ACCESSIBILITY_DISPLAY_INVERSION_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_DALTONIZER,
-                    ACCESSIBILITY_DISPLAY_DALTONIZER_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                    ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
-                    ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
-                    ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED_VALIDATOR);
-            VALIDATORS.put(AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                    ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE_VALIDATOR);
-            VALIDATORS.put(ENABLED_ACCESSIBILITY_SERVICES,
-                    ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR);
-            VALIDATORS.put(ENABLED_VR_LISTENERS, ENABLED_VR_LISTENERS_VALIDATOR);
-            VALIDATORS.put(TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                    TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR);
-            VALIDATORS.put(TOUCH_EXPLORATION_ENABLED, TOUCH_EXPLORATION_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_ENABLED, ACCESSIBILITY_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                    ACCESSIBILITY_SHORTCUT_TARGET_SERVICE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
-                    ACCESSIBILITY_BUTTON_TARGET_COMPONENT_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
-                    ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_SHORTCUT_ENABLED,
-                    ACCESSIBILITY_SHORTCUT_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
-                    ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
-                    ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_PRESET,
-                    ACCESSIBILITY_CAPTIONING_PRESET_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_ENABLED,
-                    ACCESSIBILITY_CAPTIONING_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_LOCALE,
-                    ACCESSIBILITY_CAPTIONING_LOCALE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
-                    ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
-                    ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
-                    ACCESSIBILITY_CAPTIONING_EDGE_TYPE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
-                    ACCESSIBILITY_CAPTIONING_EDGE_COLOR_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_TYPEFACE,
-                    ACCESSIBILITY_CAPTIONING_TYPEFACE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_FONT_SCALE,
-                    ACCESSIBILITY_CAPTIONING_FONT_SCALE_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
-                    ACCESSIBILITY_CAPTIONING_WINDOW_COLOR_VALIDATOR);
-            VALIDATORS.put(TTS_DEFAULT_RATE, TTS_DEFAULT_RATE_VALIDATOR);
-            VALIDATORS.put(TTS_DEFAULT_PITCH, TTS_DEFAULT_PITCH_VALIDATOR);
-            VALIDATORS.put(TTS_DEFAULT_SYNTH, TTS_DEFAULT_SYNTH_VALIDATOR);
-            VALIDATORS.put(TTS_ENABLED_PLUGINS, TTS_ENABLED_PLUGINS_VALIDATOR);
-            VALIDATORS.put(TTS_DEFAULT_LOCALE, TTS_DEFAULT_LOCALE_VALIDATOR);
-            VALIDATORS.put(SHOW_IME_WITH_HARD_KEYBOARD, SHOW_IME_WITH_HARD_KEYBOARD_VALIDATOR);
-            VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                    WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
-            VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
-                    WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR);
-            VALIDATORS.put(WIFI_NUM_OPEN_NETWORKS_KEPT, WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR);
-            VALIDATORS.put(MOUNT_PLAY_NOTIFICATION_SND, MOUNT_PLAY_NOTIFICATION_SND_VALIDATOR);
-            VALIDATORS.put(MOUNT_UMS_AUTOSTART, MOUNT_UMS_AUTOSTART_VALIDATOR);
-            VALIDATORS.put(MOUNT_UMS_PROMPT, MOUNT_UMS_PROMPT_VALIDATOR);
-            VALIDATORS.put(MOUNT_UMS_NOTIFY_ENABLED, MOUNT_UMS_NOTIFY_ENABLED_VALIDATOR);
-            VALIDATORS.put(DOUBLE_TAP_TO_WAKE, DOUBLE_TAP_TO_WAKE_VALIDATOR);
-            VALIDATORS.put(WAKE_GESTURE_ENABLED, WAKE_GESTURE_ENABLED_VALIDATOR);
-            VALIDATORS.put(LONG_PRESS_TIMEOUT, LONG_PRESS_TIMEOUT_VALIDATOR);
-            VALIDATORS.put(CAMERA_GESTURE_DISABLED, CAMERA_GESTURE_DISABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_AUTOCLICK_ENABLED,
-                    ACCESSIBILITY_AUTOCLICK_ENABLED_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_AUTOCLICK_DELAY, ACCESSIBILITY_AUTOCLICK_DELAY_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_LARGE_POINTER_ICON,
-                    ACCESSIBILITY_LARGE_POINTER_ICON_VALIDATOR);
-            VALIDATORS.put(PREFERRED_TTY_MODE, PREFERRED_TTY_MODE_VALIDATOR);
-            VALIDATORS.put(ENHANCED_VOICE_PRIVACY_ENABLED,
-                    ENHANCED_VOICE_PRIVACY_ENABLED_VALIDATOR);
-            VALIDATORS.put(TTY_MODE_ENABLED, TTY_MODE_ENABLED_VALIDATOR);
-            VALIDATORS.put(RTT_CALLING_MODE, RTT_CALLING_MODE_VALIDATOR);
-            VALIDATORS.put(INCALL_POWER_BUTTON_BEHAVIOR, INCALL_POWER_BUTTON_BEHAVIOR_VALIDATOR);
-            VALIDATORS.put(NIGHT_DISPLAY_CUSTOM_START_TIME,
-                    NIGHT_DISPLAY_CUSTOM_START_TIME_VALIDATOR);
-            VALIDATORS.put(NIGHT_DISPLAY_CUSTOM_END_TIME, NIGHT_DISPLAY_CUSTOM_END_TIME_VALIDATOR);
-            VALIDATORS.put(NIGHT_DISPLAY_COLOR_TEMPERATURE,
-                    NIGHT_DISPLAY_COLOR_TEMPERATURE_VALIDATOR);
-            VALIDATORS.put(NIGHT_DISPLAY_AUTO_MODE, NIGHT_DISPLAY_AUTO_MODE_VALIDATOR);
-            VALIDATORS.put(DISPLAY_WHITE_BALANCE_ENABLED, DISPLAY_WHITE_BALANCE_ENABLED_VALIDATOR);
-            VALIDATORS.put(SYNC_PARENT_SOUNDS, SYNC_PARENT_SOUNDS_VALIDATOR);
-            VALIDATORS.put(CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
-                    CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED_VALIDATOR);
-            VALIDATORS.put(CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
-                    CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED_VALIDATOR);
-            VALIDATORS.put(SYSTEM_NAVIGATION_KEYS_ENABLED,
-                    SYSTEM_NAVIGATION_KEYS_ENABLED_VALIDATOR);
-            VALIDATORS.put(QS_TILES, QS_TILES_VALIDATOR);
-            VALIDATORS.put(DOZE_ENABLED, DOZE_ENABLED_VALIDATOR);
-            VALIDATORS.put(DOZE_ALWAYS_ON, DOZE_ALWAYS_ON_VALIDATOR);
-            VALIDATORS.put(DOZE_PICK_UP_GESTURE, DOZE_PICK_UP_GESTURE_VALIDATOR);
-            VALIDATORS.put(DOZE_DOUBLE_TAP_GESTURE, DOZE_DOUBLE_TAP_GESTURE_VALIDATOR);
-            VALIDATORS.put(DOZE_TAP_SCREEN_GESTURE, DOZE_TAP_SCREEN_GESTURE_VALIDATOR);
-            VALIDATORS.put(DOZE_WAKE_LOCK_SCREEN_GESTURE, DOZE_WAKE_LOCK_SCREEN_GESTURE_VALIDATOR);
-            VALIDATORS.put(DOZE_WAKE_DISPLAY_GESTURE, DOZE_WAKE_DISPLAY_GESTURE_VALIDATOR);
-            VALIDATORS.put(NFC_PAYMENT_DEFAULT_COMPONENT, NFC_PAYMENT_DEFAULT_COMPONENT_VALIDATOR);
-            VALIDATORS.put(AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
-                    AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN_VALIDATOR);
-            VALIDATORS.put(FACE_UNLOCK_KEYGUARD_ENABLED, FACE_UNLOCK_KEYGUARD_ENABLED_VALIDATOR);
-            VALIDATORS.put(FACE_UNLOCK_DISMISSES_KEYGUARD,
-                    FACE_UNLOCK_DISMISSES_KEYGUARD_VALIDATOR);
-            VALIDATORS.put(SHOW_MEDIA_WHEN_BYPASSING, SHOW_MEDIA_WHEN_BYPASSING_VALIDATOR);
-            VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
-            VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
-                    FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
-            VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
-            VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
-                    ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
-            VALIDATORS.put(ASSIST_GESTURE_WAKE_ENABLED, ASSIST_GESTURE_WAKE_ENABLED_VALIDATOR);
-            VALIDATORS.put(VR_DISPLAY_MODE, VR_DISPLAY_MODE_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_BADGING, NOTIFICATION_BADGING_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_BUBBLES, NOTIFICATION_BUBBLES_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_DISMISS_RTL, NOTIFICATION_DISMISS_RTL_VALIDATOR);
-            VALIDATORS.put(QS_AUTO_ADDED_TILES, QS_AUTO_ADDED_TILES_VALIDATOR);
-            VALIDATORS.put(SCREENSAVER_ENABLED, SCREENSAVER_ENABLED_VALIDATOR);
-            VALIDATORS.put(SCREENSAVER_COMPONENTS, SCREENSAVER_COMPONENTS_VALIDATOR);
-            VALIDATORS.put(SCREENSAVER_ACTIVATE_ON_DOCK, SCREENSAVER_ACTIVATE_ON_DOCK_VALIDATOR);
-            VALIDATORS.put(SCREENSAVER_ACTIVATE_ON_SLEEP, SCREENSAVER_ACTIVATE_ON_SLEEP_VALIDATOR);
-            VALIDATORS.put(LOCKDOWN_IN_POWER_MENU, LOCKDOWN_IN_POWER_MENU_VALIDATOR);
-            VALIDATORS.put(SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
-                    SHOW_FIRST_CRASH_DIALOG_DEV_OPTION_VALIDATOR);
-            VALIDATORS.put(VOLUME_HUSH_GESTURE, VOLUME_HUSH_GESTURE_VALIDATOR);
-            VALIDATORS.put(ENABLED_NOTIFICATION_LISTENERS,
-                    ENABLED_NOTIFICATION_LISTENERS_VALIDATOR); //legacy restore setting
-            VALIDATORS.put(ENABLED_NOTIFICATION_ASSISTANT,
-                    ENABLED_NOTIFICATION_ASSISTANT_VALIDATOR); //legacy restore setting
-            VALIDATORS.put(ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                    ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES_VALIDATOR); //legacy restore setting
-            VALIDATORS.put(HUSH_GESTURE_USED, HUSH_GESTURE_USED_VALIDATOR);
-            VALIDATORS.put(MANUAL_RINGER_TOGGLE_COUNT, MANUAL_RINGER_TOGGLE_COUNT_VALIDATOR);
-            VALIDATORS.put(IN_CALL_NOTIFICATION_ENABLED, IN_CALL_NOTIFICATION_ENABLED_VALIDATOR);
-            VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR);
-            VALIDATORS.put(SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
-                    NON_NEGATIVE_INTEGER_VALIDATOR);
-            VALIDATORS.put(ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
-            VALIDATORS.put(USER_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(TRUST_AGENTS_EXTEND_UNLOCK, TRUST_AGENTS_EXTEND_UNLOCK_VALIDATOR);
-            VALIDATORS.put(LOCK_SCREEN_CUSTOM_CLOCK_FACE, LOCK_SCREEN_CUSTOM_CLOCK_FACE_VALIDATOR);
-            VALIDATORS.put(LOCK_SCREEN_WHEN_TRUST_LOST, LOCK_SCREEN_WHEN_TRUST_LOST_VALIDATOR);
-            VALIDATORS.put(SKIP_GESTURE, SKIP_GESTURE_VALIDATOR);
-            VALIDATORS.put(SKIP_DIRECTION, SKIP_DIRECTION_VALIDATOR);
-            VALIDATORS.put(SKIP_DIRECTION, SKIP_DIRECTION_VALIDATOR);
-            VALIDATORS.put(SILENCE_GESTURE, SILENCE_GESTURE_VALIDATOR);
-            VALIDATORS.put(THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
-                    THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR);
-            VALIDATORS.put(NAVIGATION_MODE, NAVIGATION_MODE_VALIDATOR);
-            VALIDATORS.put(AWARE_ENABLED, AWARE_ENABLED_VALIDATOR);
-            VALIDATORS.put(SKIP_GESTURE_COUNT, SKIP_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SKIP_TOUCH_COUNT, SKIP_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_ALARMS_GESTURE_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_TIMER_GESTURE_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_CALL_GESTURE_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_ALARMS_TOUCH_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_TIMER_TOUCH_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(SILENCE_CALL_TOUCH_COUNT, SILENCE_GESTURE_COUNT_VALIDATOR);
-            VALIDATORS.put(ODI_CAPTIONS_ENABLED, ODI_CAPTIONS_ENABLED_VALIDATOR);
-            VALIDATORS.put(DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(UI_NIGHT_MODE, UI_NIGHT_MODE_VALIDATOR);
-            VALIDATORS.put(GLOBAL_ACTIONS_PANEL_ENABLED, GLOBAL_ACTIONS_PANEL_ENABLED_VALIDATOR);
-            VALIDATORS.put(AWARE_LOCK_ENABLED, AWARE_LOCK_ENABLED_VALIDATOR);
-            VALIDATORS.put(DISPLAY_DENSITY_FORCED, DISPLAY_DENSITY_FORCED_VALIDATOR);
-        }
-
-        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -9373,6 +8225,14 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
 
         /**
+         * Whether the notification bubbles are globally enabled
+         * The value is boolean (1 or 0).
+         * @hide
+         */
+        @TestApi
+        public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+
+        /**
          * Whether users are allowed to add more users or guest from lockscreen.
          * <p>
          * Type: int
@@ -9387,8 +8247,6 @@
          */
         public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
 
-        private static final Validator APPLY_RAMPING_RINGER_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Setting whether the global gesture for enabling accessibility is enabled.
          * If this gesture is enabled the user will be able to perfrom it to enable
@@ -9516,16 +8374,12 @@
          */
         public static final String AUTO_TIME = "auto_time";
 
-        private static final Validator AUTO_TIME_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Value to specify if the user prefers the time zone
          * to be automatically fetched from the network (NITZ). 1=yes, 0=no
          */
         public static final String AUTO_TIME_ZONE = "auto_time_zone";
 
-        private static final Validator AUTO_TIME_ZONE_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * URI for the car dock "in" event sound.
          * @hide
@@ -9556,8 +8410,6 @@
          */
         public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
 
-        private static final Validator DOCK_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether to play a sound for dock events, only when an accessibility service is on.
          * @hide
@@ -9595,8 +8447,6 @@
          */
         public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
 
-        private static final Validator POWER_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * URI for the "wireless charging started" and "wired charging started" sound.
          * @hide
@@ -9612,8 +8462,6 @@
         @Deprecated
         public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled";
 
-        private static final Validator CHARGING_SOUNDS_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether to vibrate for wireless charging events.
          * @deprecated Use {@link android.provider.Settings.Secure#CHARGING_VIBRATION_ENABLED}
@@ -9622,8 +8470,6 @@
         @Deprecated
         public static final String CHARGING_VIBRATION_ENABLED = "charging_vibration_enabled";
 
-        private static final Validator CHARGING_VIBRATION_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether we keep the device on while the device is plugged in.
          * Supported values are:
@@ -9637,30 +8483,6 @@
          */
         public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
 
-        private static final Validator STAY_ON_WHILE_PLUGGED_IN_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                try {
-                    int val = Integer.parseInt(value);
-                    return (val == 0)
-                            || (val == BatteryManager.BATTERY_PLUGGED_AC)
-                            || (val == BatteryManager.BATTERY_PLUGGED_USB)
-                            || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_USB))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_USB
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS))
-                            || (val == (BatteryManager.BATTERY_PLUGGED_AC
-                                    | BatteryManager.BATTERY_PLUGGED_USB
-                                    | BatteryManager.BATTERY_PLUGGED_WIRELESS));
-                } catch (NumberFormatException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * When the user has enable the option to have a "bug report" command
          * in the power menu.
@@ -9668,8 +8490,6 @@
          */
         public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
 
-        private static final Validator BUGREPORT_IN_POWER_MENU_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Whether ADB is enabled.
          */
@@ -9700,8 +8520,6 @@
          */
         public static final String BLUETOOTH_ON = "bluetooth_on";
 
-        private static final Validator BLUETOOTH_ON_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * CDMA Cell Broadcast SMS
          *                            0 = CDMA Cell Broadcast SMS disabled
@@ -9981,13 +8799,6 @@
                 "location_ignore_settings_package_whitelist";
 
         /**
-         * Whether to disable location status callbacks in preparation for deprecation.
-         * @hide
-         */
-        public static final String LOCATION_DISABLE_STATUS_CALLBACKS =
-                "location_disable_status_callbacks";
-
-        /**
          * Maximum staleness allowed for last location when returned to clients with only foreground
          * location permissions.
          * @hide
@@ -10426,8 +9237,6 @@
         */
        public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
 
-       private static final Validator USB_MASS_STORAGE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
        /**
         * If this setting is set (to anything), then all references
         * to Gmail on the device must change to Google Mail.
@@ -10580,9 +9389,6 @@
        public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
                "wifi_networks_available_notification_on";
 
-       private static final Validator WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
-               BOOLEAN_VALIDATOR;
-
        /**
         * {@hide}
         */
@@ -10596,9 +9402,6 @@
        public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
                "wifi_networks_available_repeat_delay";
 
-       private static final Validator WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY_VALIDATOR =
-               NON_NEGATIVE_INTEGER_VALIDATOR;
-
        /**
         * 802.11 country code in ISO 3166 format
         * @hide
@@ -10628,9 +9431,6 @@
         */
        public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
 
-       private static final Validator WIFI_NUM_OPEN_NETWORKS_KEPT_VALIDATOR =
-               NON_NEGATIVE_INTEGER_VALIDATOR;
-
        /**
         * Whether the Wi-Fi should be on.  Only the Wi-Fi service should touch this.
         */
@@ -10669,8 +9469,6 @@
          */
         public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
 
-        private static final Validator SOFT_AP_TIMEOUT_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Value to specify if Wi-Fi Wakeup feature is enabled.
          *
@@ -10680,8 +9478,6 @@
         @SystemApi
         public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
 
-        private static final Validator WIFI_WAKEUP_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Value to specify whether network quality scores and badging should be shown in the UI.
          *
@@ -10717,8 +9513,6 @@
         public static final String NETWORK_RECOMMENDATIONS_ENABLED =
                 "network_recommendations_enabled";
 
-        private static final Validator NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"-1", "0", "1"});
 
         /**
          * Which package name to use for network recommendations. If null, network recommendations
@@ -10743,13 +9537,6 @@
         @TestApi
         public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
 
-        private static final Validator USE_OPEN_WIFI_PACKAGE_VALIDATOR = new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                return (value == null) || PACKAGE_NAME_VALIDATOR.validate(value);
-            }
-        };
-
         /**
          * The number of milliseconds the {@link com.android.server.NetworkScoreService}
          * will give a recommendation request to complete before returning a default response.
@@ -10781,8 +9568,6 @@
          */
         public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
 
-        private static final Validator WIFI_SCAN_THROTTLE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
         * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
         * connectivity.
@@ -10888,9 +9673,6 @@
        public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
                "wifi_watchdog_poor_network_test_enabled";
 
-       private static final Validator WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED_VALIDATOR =
-               ANY_STRING_VALIDATOR;
-
        /**
         * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
         * needs to be set to 0 to disable it.
@@ -10974,9 +9756,6 @@
         public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
                 "wifi_pno_frequency_culling_enabled";
 
-        private static final Validator WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting to enable including recency information when determining pno network priorities.
          * Disabled by default, and setting it to 1 will enable it.
@@ -10986,9 +9765,6 @@
         public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
                 "wifi_pno_recency_sorting_enabled";
 
-        private static final Validator WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Setting to enable the Wi-Fi link probing.
          * Enabled by default, and setting it to 0 will disable it.
@@ -10998,9 +9774,6 @@
         public static final String WIFI_LINK_PROBING_ENABLED =
                 "wifi_link_probing_enabled";
 
-        private static final Validator WIFI_LINK_PROBING_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
        /**
         * The maximum number of times we will retry a connection to an access
         * point for which we have failed in acquiring an IP address from DHCP.
@@ -11540,15 +10313,11 @@
          */
         public static final String PRIVATE_DNS_MODE = "private_dns_mode";
 
-        private static final Validator PRIVATE_DNS_MODE_VALIDATOR = ANY_STRING_VALIDATOR;
-
         /**
          * @hide
          */
         public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
 
-        private static final Validator PRIVATE_DNS_SPECIFIER_VALIDATOR = ANY_STRING_VALIDATOR;
-
         /**
           * Forced override of the default mode (hardcoded as "automatic", nee "opportunistic").
           * This allows changing the default mode without effectively disabling other modes,
@@ -12291,9 +11060,6 @@
         public static final String APP_AUTO_RESTRICTION_ENABLED =
                 "app_auto_restriction_enabled";
 
-        private static final Validator APP_AUTO_RESTRICTION_ENABLED_VALIDATOR =
-                BOOLEAN_VALIDATOR;
-
         /**
          * Feature flag to enable or disable the Forced App Standby feature.
          * Type: int (0 for false, 1 for true)
@@ -12568,9 +11334,6 @@
          */
         public static final String EMERGENCY_TONE = "emergency_tone";
 
-        private static final Validator EMERGENCY_TONE_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1", "2"});
-
         /**
          * CDMA only settings
          * Whether the auto retry is enabled. The value is
@@ -12579,8 +11342,6 @@
          */
         public static final String CALL_AUTO_RETRY = "call_auto_retry";
 
-        private static final Validator CALL_AUTO_RETRY_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * A setting that can be read whether the emergency affordance is currently needed.
          * The value is a boolean (1 or 0).
@@ -12598,9 +11359,6 @@
         public static final String ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS =
                 "enable_automatic_system_server_heap_dumps";
 
-        private static final Validator ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1"});
-
         /**
          * See RIL_PreferredNetworkType in ril.h
          * @hide
@@ -12792,9 +11550,6 @@
         public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL =
                 "low_power_sticky_auto_disable_level";
 
-        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 100);
-
         /**
          * Whether sticky battery saver should be deactivated once the battery level has reached the
          * threshold specified by {@link #LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL}.
@@ -12804,9 +11559,6 @@
         public static final String LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED =
                 "low_power_sticky_auto_disable_enabled";
 
-        private static final Validator LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1"});
-
         /**
          * Battery level [1-100] at which low power mode automatically turns on.
          * If 0, it will not automatically turn on. For Q and newer, it will only automatically
@@ -12819,9 +11571,6 @@
          */
         public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";
 
-        private static final Validator LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 100);
-
         /**
          * Whether battery saver is currently set to trigger based on percentage, dynamic power
          * savings trigger, or none. See {@link AutoPowerSaveModeTriggers} for
@@ -12832,9 +11581,6 @@
         @TestApi
         public static final String AUTOMATIC_POWER_SAVE_MODE = "automatic_power_save_mode";
 
-        private static final Validator AUTOMATIC_POWER_SAVE_MODE_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1"});
-
         /**
          * The setting that backs the disable threshold for the setPowerSavingsWarning api in
          * PowerManager
@@ -12845,8 +11591,6 @@
         @TestApi
         public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD =
                 "dynamic_power_savings_disable_threshold";
-        private static final Validator DYNAMIC_POWER_SAVINGS_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 100);
 
         /**
          * The setting which backs the setDynamicPowerSaveHint api in PowerManager.
@@ -12934,8 +11678,6 @@
          */
         public static final String DOCK_AUDIO_MEDIA_ENABLED = "dock_audio_media_enabled";
 
-        private static final Validator DOCK_AUDIO_MEDIA_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * The surround sound formats AC3, DTS or IEC61937 are
          * available for use if they are detected.
@@ -12995,9 +11737,6 @@
          */
         public static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output";
 
-        private static final Validator ENCODED_SURROUND_OUTPUT_VALIDATOR =
-                new DiscreteValueValidator(new String[] {"0", "1", "2", "3"});
-
         /**
          * Surround sounds formats that are enabled when ENCODED_SURROUND_OUTPUT is set to
          * ENCODED_SURROUND_OUTPUT_MANUAL. Encoded as comma separated list. Allowed values
@@ -13010,32 +11749,6 @@
         public static final String ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS =
                 "encoded_surround_output_enabled_formats";
 
-        private static final Validator ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR =
-                new Validator() {
-            @Override
-            public boolean validate(@Nullable String value) {
-                try {
-                    String[] surroundFormats = TextUtils.split(value, ",");
-                    for (String format : surroundFormats) {
-                        int audioFormat = Integer.valueOf(format);
-                        boolean isSurroundFormat = false;
-                        for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
-                            if (sf == audioFormat) {
-                                isSurroundFormat = true;
-                                break;
-                            }
-                        }
-                        if (!isSurroundFormat) {
-                            return false;
-                        }
-                    }
-                    return true;
-                } catch (NumberFormatException e) {
-                    return false;
-                }
-            }
-        };
-
         /**
          * Persisted safe headphone volume management state by AudioService
          * @hide
@@ -13278,8 +11991,6 @@
         @Deprecated
         public static final String ZEN_DURATION = "zen_duration";
 
-        private static final Validator ZEN_DURATION_VALIDATOR = ANY_INTEGER_VALIDATOR;
-
         /**
          * @deprecated Use {@link android.provider.Settings.Secure#ZEN_DURATION_PROMPT} instead
          * @hide
@@ -13736,8 +12447,6 @@
          */
         public static final String AWARE_ALLOWED = "aware_allowed";
 
-        private static final Validator AWARE_ALLOWED_VALIDATOR = BOOLEAN_VALIDATOR;
-
         /**
          * Overrides internal R.integer.config_longPressOnPowerBehavior.
          * Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
@@ -13746,8 +12455,6 @@
          */
         public static final String POWER_BUTTON_LONG_PRESS =
                 "power_button_long_press";
-        private static final Validator POWER_BUTTON_LONG_PRESS_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 5);
 
         /**
          * Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
@@ -13757,126 +12464,6 @@
          */
         public static final String POWER_BUTTON_VERY_LONG_PRESS =
                 "power_button_very_long_press";
-        private static final Validator POWER_BUTTON_VERY_LONG_PRESS_VALIDATOR =
-                new InclusiveIntegerRangeValidator(0, 1);
-
-        /**
-         * Settings to backup. This is here so that it's in the same place as the settings
-         * keys and easy to update.
-         *
-         * These keys may be mentioned in the SETTINGS_TO_BACKUP arrays in System
-         * and Secure as well.  This is because those tables drive both backup and
-         * restore, and restore needs to properly whitelist keys that used to live
-         * in those namespaces.  The keys will only actually be backed up / restored
-         * if they are also mentioned in this table (Global.SETTINGS_TO_BACKUP).
-         *
-         * NOTE: Settings are backed up and restored in the order they appear
-         *       in this array. If you have one setting depending on another,
-         *       make sure that they are ordered appropriately.
-         *
-         * NOTE: This table should only be used for settings which should be restored
-         *       between different types of devices {@see #DEVICE_SPECIFIC_SETTINGS_TO_BACKUP}
-         *
-         * @hide
-         */
-        public static final String[] SETTINGS_TO_BACKUP = {
-            APPLY_RAMPING_RINGER,
-            BUGREPORT_IN_POWER_MENU,
-            STAY_ON_WHILE_PLUGGED_IN,
-            APP_AUTO_RESTRICTION_ENABLED,
-            AUTO_TIME,
-            AUTO_TIME_ZONE,
-            POWER_SOUNDS_ENABLED,
-            DOCK_SOUNDS_ENABLED,
-            CHARGING_SOUNDS_ENABLED,
-            USB_MASS_STORAGE_ENABLED,
-            NETWORK_RECOMMENDATIONS_ENABLED,
-            WIFI_WAKEUP_ENABLED,
-            WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-            USE_OPEN_WIFI_PACKAGE,
-            WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
-            EMERGENCY_TONE,
-            CALL_AUTO_RETRY,
-            DOCK_AUDIO_MEDIA_ENABLED,
-            ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS,
-            ENCODED_SURROUND_OUTPUT,
-            ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
-            LOW_POWER_MODE_TRIGGER_LEVEL,
-            LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
-            LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
-            BLUETOOTH_ON,
-            PRIVATE_DNS_MODE,
-            PRIVATE_DNS_SPECIFIER,
-            SOFT_AP_TIMEOUT_ENABLED,
-            ZEN_DURATION,
-            CHARGING_VIBRATION_ENABLED,
-            AWARE_ALLOWED,
-        };
-
-        /**
-         * All settings in {@link SETTINGS_TO_BACKUP} array *must* have a non-null validator,
-         * otherwise they won't be restored.
-         *
-         * @hide
-         */
-        public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
-
-        static {
-            VALIDATORS.put(APPLY_RAMPING_RINGER, APPLY_RAMPING_RINGER_VALIDATOR);
-            VALIDATORS.put(BUGREPORT_IN_POWER_MENU, BUGREPORT_IN_POWER_MENU_VALIDATOR);
-            VALIDATORS.put(STAY_ON_WHILE_PLUGGED_IN, STAY_ON_WHILE_PLUGGED_IN_VALIDATOR);
-            VALIDATORS.put(AUTO_TIME, AUTO_TIME_VALIDATOR);
-            VALIDATORS.put(AUTO_TIME_ZONE, AUTO_TIME_ZONE_VALIDATOR);
-            VALIDATORS.put(POWER_SOUNDS_ENABLED, POWER_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(DOCK_SOUNDS_ENABLED, DOCK_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(CHARGING_SOUNDS_ENABLED, CHARGING_SOUNDS_ENABLED_VALIDATOR);
-            VALIDATORS.put(USB_MASS_STORAGE_ENABLED, USB_MASS_STORAGE_ENABLED_VALIDATOR);
-            VALIDATORS.put(NETWORK_RECOMMENDATIONS_ENABLED,
-                    NETWORK_RECOMMENDATIONS_ENABLED_VALIDATOR);
-            VALIDATORS.put(WIFI_WAKEUP_ENABLED, WIFI_WAKEUP_ENABLED_VALIDATOR);
-            VALIDATORS.put(WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                    WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
-            VALIDATORS.put(USE_OPEN_WIFI_PACKAGE, USE_OPEN_WIFI_PACKAGE_VALIDATOR);
-            VALIDATORS.put(WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
-                    WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED_VALIDATOR);
-            VALIDATORS.put(EMERGENCY_TONE, EMERGENCY_TONE_VALIDATOR);
-            VALIDATORS.put(CALL_AUTO_RETRY, CALL_AUTO_RETRY_VALIDATOR);
-            VALIDATORS.put(DOCK_AUDIO_MEDIA_ENABLED, DOCK_AUDIO_MEDIA_ENABLED_VALIDATOR);
-            VALIDATORS.put(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS,
-                    ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_VALIDATOR);
-            VALIDATORS.put(ENCODED_SURROUND_OUTPUT, ENCODED_SURROUND_OUTPUT_VALIDATOR);
-            VALIDATORS.put(ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
-                    ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS_VALIDATOR);
-            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
-                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL_VALIDATOR);
-            VALIDATORS.put(LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
-                    LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED_VALIDATOR);
-            VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL, LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
-            VALIDATORS.put(LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
-                    LOW_POWER_MODE_TRIGGER_LEVEL_VALIDATOR);
-            VALIDATORS.put(AUTOMATIC_POWER_SAVE_MODE, AUTOMATIC_POWER_SAVE_MODE_VALIDATOR);
-            VALIDATORS.put(DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
-                    DYNAMIC_POWER_SAVINGS_VALIDATOR);
-            VALIDATORS.put(BLUETOOTH_ON, BLUETOOTH_ON_VALIDATOR);
-            VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
-            VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
-            VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
-            VALIDATORS.put(WIFI_SCAN_THROTTLE_ENABLED, WIFI_SCAN_THROTTLE_ENABLED_VALIDATOR);
-            VALIDATORS.put(APP_AUTO_RESTRICTION_ENABLED, APP_AUTO_RESTRICTION_ENABLED_VALIDATOR);
-            VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR);
-            VALIDATORS.put(CHARGING_VIBRATION_ENABLED, CHARGING_VIBRATION_ENABLED_VALIDATOR);
-            VALIDATORS.put(DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
-            VALIDATORS.put(WIFI_PNO_FREQUENCY_CULLING_ENABLED,
-                    WIFI_PNO_FREQUENCY_CULLING_ENABLED_VALIDATOR);
-            VALIDATORS.put(WIFI_PNO_RECENCY_SORTING_ENABLED,
-                    WIFI_PNO_RECENCY_SORTING_ENABLED_VALIDATOR);
-            VALIDATORS.put(WIFI_LINK_PROBING_ENABLED, WIFI_LINK_PROBING_ENABLED_VALIDATOR);
-            VALIDATORS.put(AWARE_ALLOWED, AWARE_ALLOWED_VALIDATOR);
-            VALIDATORS.put(POWER_BUTTON_LONG_PRESS, POWER_BUTTON_LONG_PRESS_VALIDATOR);
-            VALIDATORS.put(POWER_BUTTON_VERY_LONG_PRESS, POWER_BUTTON_VERY_LONG_PRESS_VALIDATOR);
-        }
 
         /**
          * Global settings that shouldn't be persisted.
@@ -14321,8 +12908,7 @@
         }
 
         /**
-          * Subscription to be used for voice call on a multi sim device. The supported values
-          * are 0 = SUB1, 1 = SUB2 and etc.
+          * Subscription Id to be used for voice call on a multi sim device.
           * @hide
           */
         public static final String MULTI_SIM_VOICE_CALL_SUBSCRIPTION = "multi_sim_voice_call";
@@ -14336,15 +12922,13 @@
         public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt";
 
         /**
-          * Subscription to be used for data call on a multi sim device. The supported values
-          * are 0 = SUB1, 1 = SUB2 and etc.
+          * Subscription Id to be used for data call on a multi sim device.
           * @hide
           */
         public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call";
 
         /**
-          * Subscription to be used for SMS on a multi sim device. The supported values
-          * are 0 = SUB1, 1 = SUB2 and etc.
+          * Subscription Id to be used for SMS on a multi sim device.
           * @hide
           */
         public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms";
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 52a6a45..ae2836f 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -4,9 +4,21 @@
             "name": "CtsProviderTestCases",
             "options": [
                 {
-                    "include-annotation": "android.platform.test.annotations.Presubmit"
+                    "exclude-filter": "android.provider.cts.SettingsPanelTest"
                 }
             ]
+        },
+        {
+            "name": "CalendarProviderTests"
+        },
+        {
+            "name": "ContactsProviderTests"
+        },
+        {
+            "name": "MediaProviderTests"
+        },
+        {
+            "name": "SettingsProviderTest"
         }
     ]
 }
diff --git a/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java
deleted file mode 100644
index 38400ac..0000000
--- a/core/java/android/provider/settings/validators/InclusiveFloatRangeValidator.java
+++ /dev/null
@@ -1,44 +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 android.provider.settings.validators;
-
-import android.annotation.Nullable;
-
-/**
- * Validate a float value lies within a given (boundary inclusive) range.
- *
- * @hide
- */
-public final class InclusiveFloatRangeValidator implements Validator {
-    private final float mMin;
-    private final float mMax;
-
-    public InclusiveFloatRangeValidator(float min, float max) {
-        mMin = min;
-        mMax = max;
-    }
-
-    @Override
-    public boolean validate(@Nullable String value) {
-        try {
-            final float floatValue = Float.parseFloat(value);
-            return floatValue >= mMin && floatValue <= mMax;
-        } catch (NumberFormatException | NullPointerException e) {
-            return false;
-        }
-    }
-}
diff --git a/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java b/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
deleted file mode 100644
index e53c252..0000000
--- a/core/java/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
+++ /dev/null
@@ -1,44 +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 android.provider.settings.validators;
-
-import android.annotation.Nullable;
-
-/**
- * Validate an integer value lies within a given (boundary inclusive) range.
- *
- * @hide
- */
-public final class InclusiveIntegerRangeValidator implements Validator {
-    private final int mMin;
-    private final int mMax;
-
-    public InclusiveIntegerRangeValidator(int min, int max) {
-        mMin = min;
-        mMax = max;
-    }
-
-    @Override
-    public boolean validate(@Nullable String value) {
-        try {
-            final int intValue = Integer.parseInt(value);
-            return intValue >= mMin && intValue <= mMax;
-        } catch (NumberFormatException e) {
-            return false;
-        }
-    }
-}
diff --git a/core/java/android/provider/settings/validators/PackageNameListValidator.java b/core/java/android/provider/settings/validators/PackageNameListValidator.java
deleted file mode 100644
index bc7fc13..0000000
--- a/core/java/android/provider/settings/validators/PackageNameListValidator.java
+++ /dev/null
@@ -1,40 +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 android.provider.settings.validators;
-
-import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
-
-/**
- * Validate a list of package names.
- *
- * @hide
- */
-public final class PackageNameListValidator extends ListValidator {
-    public PackageNameListValidator(String separator) {
-        super(separator);
-    }
-
-    @Override
-    protected boolean isEntryValid(String entry) {
-        return entry != null;
-    }
-
-    @Override
-    protected boolean isItemValid(String item) {
-        return PACKAGE_NAME_VALIDATOR.validate(item);
-    }
-}
diff --git a/core/java/android/provider/settings/validators/SettingsValidators.java b/core/java/android/provider/settings/validators/SettingsValidators.java
deleted file mode 100644
index 562c638..0000000
--- a/core/java/android/provider/settings/validators/SettingsValidators.java
+++ /dev/null
@@ -1,183 +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 android.provider.settings.validators;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Locale;
-
-/**
- * This class provides both interface for validation and common validators
- * used to ensure Settings have meaningful values.
- *
- * @hide
- */
-public class SettingsValidators {
-
-    public static final Validator BOOLEAN_VALIDATOR =
-            new DiscreteValueValidator(new String[] {"0", "1"});
-
-    public static final Validator ANY_STRING_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            return true;
-        }
-    };
-
-    public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            try {
-                return Integer.parseInt(value) >= 0;
-            } catch (NumberFormatException e) {
-                return false;
-            }
-        }
-    };
-
-    public static final Validator ANY_INTEGER_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            try {
-                Integer.parseInt(value);
-                return true;
-            } catch (NumberFormatException e) {
-                return false;
-            }
-        }
-    };
-
-    public static final Validator URI_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            try {
-                Uri.decode(value);
-                return true;
-            } catch (IllegalArgumentException e) {
-                return false;
-            }
-        }
-    };
-
-    /**
-     * Does not allow a setting to have a null {@link ComponentName}. Use {@link
-     * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a
-     * nullable {@link ComponentName}.
-     */
-    public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            return value != null && ComponentName.unflattenFromString(value) != null;
-        }
-    };
-
-    /**
-     * Allows a setting to have a null {@link ComponentName}.
-     */
-    public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            return value == null || COMPONENT_NAME_VALIDATOR.validate(value);
-        }
-    };
-
-    public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            return value != null && isStringPackageName(value);
-        }
-
-        private boolean isStringPackageName(String value) {
-            // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
-            // and underscores ('_'). However, individual package name parts may only
-            // start with letters.
-            // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
-            if (value == null) {
-                return false;
-            }
-            String[] subparts = value.split("\\.");
-            boolean isValidPackageName = true;
-            for (String subpart : subparts) {
-                isValidPackageName &= isSubpartValidForPackageName(subpart);
-                if (!isValidPackageName) break;
-            }
-            return isValidPackageName;
-        }
-
-        private boolean isSubpartValidForPackageName(String subpart) {
-            if (subpart.length() == 0) return false;
-            boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
-            for (int i = 1; i < subpart.length(); i++) {
-                isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
-                                || (subpart.charAt(i) == '_'));
-                if (!isValidSubpart) break;
-            }
-            return isValidSubpart;
-        }
-    };
-
-    public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
-        private static final int MAX_IPV6_LENGTH = 45;
-
-        @Override
-        public boolean validate(@Nullable String value) {
-            if (value == null) {
-                return false;
-            }
-            return value.length() <= MAX_IPV6_LENGTH;
-        }
-    };
-
-    public static final Validator LOCALE_VALIDATOR = new Validator() {
-        @Override
-        public boolean validate(@Nullable String value) {
-            if (value == null) {
-                return false;
-            }
-            Locale[] validLocales = Locale.getAvailableLocales();
-            for (Locale locale : validLocales) {
-                if (value.equals(locale.toString())) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    };
-
-    /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */
-    public static final Validator JSON_OBJECT_VALIDATOR = (value) -> {
-        if (TextUtils.isEmpty(value)) {
-            return false;
-        }
-        try {
-            new JSONObject(value);
-            return true;
-        } catch (JSONException e) {
-            return false;
-        }
-    };
-
-    public static final Validator TTS_LIST_VALIDATOR = new TTSListValidator();
-
-    public static final Validator TILE_LIST_VALIDATOR = new TileListValidator();
-}
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index 9a97bb2..0b44470 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -81,6 +81,7 @@
         return mProxy.getSmartSuggestionParams();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "FillRequest[act=" + getActivityComponent().flattenToShortString()
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index 334487d..8b3a001 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -82,6 +82,7 @@
             return mBounds;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return mBounds.toString();
diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java
index 57e4b1b..0c12fd4 100644
--- a/core/java/android/service/carrier/ApnService.java
+++ b/core/java/android/service/carrier/ApnService.java
@@ -26,7 +26,7 @@
 import android.os.IBinder;
 import android.util.Log;
 
-import com.android.internal.telephony.IApnSourceService;
+import android.service.carrier.IApnSourceService;
 
 import java.util.List;
 
diff --git a/core/java/android/service/carrier/CarrierService.java b/core/java/android/service/carrier/CarrierService.java
index aeb186b..9184d6d 100644
--- a/core/java/android/service/carrier/CarrierService.java
+++ b/core/java/android/service/carrier/CarrierService.java
@@ -16,17 +16,15 @@
 
 import android.annotation.CallSuper;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PersistableBundle;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
+import android.os.telephony.TelephonyRegistryManager;
 import android.util.Log;
 
-import com.android.internal.telephony.ITelephonyRegistry;
-
 /**
  * A service that exposes carrier-specific functionality to the system.
  * <p>
@@ -55,16 +53,10 @@
 
     public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
 
-    private static ITelephonyRegistry sRegistry;
-
     private final ICarrierService.Stub mStubWrapper;
 
     public CarrierService() {
         mStubWrapper = new ICarrierServiceWrapper();
-        if (sRegistry == null) {
-            sRegistry = ITelephonyRegistry.Stub.asInterface(
-                    ServiceManager.getService("telephony.registry"));
-        }
     }
 
     /**
@@ -122,9 +114,12 @@
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
      */
     public final void notifyCarrierNetworkChange(boolean active) {
-        try {
-            if (sRegistry != null) sRegistry.notifyCarrierNetworkChange(active);
-        } catch (RemoteException | NullPointerException ex) {}
+        TelephonyRegistryManager telephonyRegistryMgr =
+            (TelephonyRegistryManager) this.getSystemService(
+                Context.TELEPHONY_REGISTRY_SERVICE);
+        if (telephonyRegistryMgr != null) {
+            telephonyRegistryMgr.notifyCarrierNetworkChange(active);
+        }
     }
 
     /**
diff --git a/core/java/android/service/carrier/IApnSourceService.aidl b/core/java/android/service/carrier/IApnSourceService.aidl
new file mode 100644
index 0000000..fadd2ff
--- /dev/null
+++ b/core/java/android/service/carrier/IApnSourceService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.carrier;
+
+import android.content.ContentValues;
+
+/** @hide */
+interface IApnSourceService {
+    /** Retreive APNs. */
+    ContentValues[] getApns(int subId);
+}
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index fc781c2..b741cff 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -111,6 +111,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("ActivityEvent[").append(mComponentName.toShortString())
diff --git a/core/java/android/service/euicc/EuiccProfileInfo.java b/core/java/android/service/euicc/EuiccProfileInfo.java
index 702837b..6c357cc 100644
--- a/core/java/android/service/euicc/EuiccProfileInfo.java
+++ b/core/java/android/service/euicc/EuiccProfileInfo.java
@@ -16,6 +16,7 @@
 package android.service.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
@@ -395,7 +396,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -430,6 +431,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "EuiccProfileInfo (nickname="
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index d2f22bf..ff8b135 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -29,7 +29,6 @@
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
 import android.telephony.euicc.EuiccManager.OtaStatus;
-import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -252,18 +251,6 @@
     public static final int RESULT_FIRST_USER = 1;
 
     /**
-     * List of all valid resolution actions for validation purposes.
-     * @hide
-     */
-    public static final ArraySet<String> RESOLUTION_ACTIONS;
-    static {
-        RESOLUTION_ACTIONS = new ArraySet<>();
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_RESOLVABLE_ERRORS);
-    }
-
-    /**
      * Boolean extra for resolution actions indicating whether the user granted consent.
      * This is used and set by the implementation and used in {@code EuiccOperation}.
      */
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index aa11445..8ab687f 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -257,6 +257,7 @@
         dest.writeString(mIssuer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "Adjustment{"
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 78e30ab..85f13d5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1522,6 +1522,7 @@
         private ArrayList<Notification.Action> mSmartActions;
         private ArrayList<CharSequence> mSmartReplies;
         private boolean mCanBubble;
+        private boolean mVisuallyInterruptive;
 
         private static final int PARCEL_VERSION = 2;
 
@@ -1553,6 +1554,7 @@
             out.writeTypedList(mSmartActions, flags);
             out.writeCharSequenceList(mSmartReplies);
             out.writeBoolean(mCanBubble);
+            out.writeBoolean(mVisuallyInterruptive);
         }
 
         /** @hide */
@@ -1585,6 +1587,7 @@
             mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
             mSmartReplies = in.readCharSequenceList();
             mCanBubble = in.readBoolean();
+            mVisuallyInterruptive = in.readBoolean();
         }
 
 
@@ -1772,6 +1775,11 @@
         }
 
         /** @hide */
+        public boolean visuallyInterruptive() {
+            return mVisuallyInterruptive;
+        }
+
+        /** @hide */
         public boolean isNoisy() {
             return mNoisy;
         }
@@ -1787,7 +1795,8 @@
                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
-                ArrayList<CharSequence> smartReplies, boolean canBubble) {
+                ArrayList<CharSequence> smartReplies, boolean canBubble,
+                boolean visuallyInterruptive) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1808,6 +1817,7 @@
             mSmartActions = smartActions;
             mSmartReplies = smartReplies;
             mCanBubble = canBubble;
+            mVisuallyInterruptive = visuallyInterruptive;
         }
 
         /**
@@ -1832,7 +1842,8 @@
                     other.mNoisy,
                     other.mSmartActions,
                     other.mSmartReplies,
-                    other.mCanBubble);
+                    other.mCanBubble,
+                    other.mVisuallyInterruptive);
         }
 
         /**
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 2b4c24c..8be114c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -16,6 +16,8 @@
 package android.service.notification;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.RemoteInput;
@@ -266,7 +268,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
@@ -293,6 +295,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("NotificationStats{");
diff --git a/core/java/android/service/notification/SnoozeCriterion.java b/core/java/android/service/notification/SnoozeCriterion.java
index 938cc10..eb624c9 100644
--- a/core/java/android/service/notification/SnoozeCriterion.java
+++ b/core/java/android/service/notification/SnoozeCriterion.java
@@ -15,6 +15,7 @@
  */
 package android.service.notification;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -118,7 +119,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 205df7e..b8378a3 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -170,10 +170,7 @@
 
     /**
      * Returns true if application asked that this notification be part of a group.
-     *
-     * @hide
      */
-    @SystemApi
     public boolean isAppGroup() {
         if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
             return true;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index bfab580..937990f7 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1639,7 +1639,7 @@
         @UnsupportedAppUsage
         public String name;              // required for automatic
         @UnsupportedAppUsage
-        public int zenMode;
+        public int zenMode;             // ie: Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
         @UnsupportedAppUsage
         public Uri conditionId;          // required for automatic
         public Condition condition;      // optional
diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java
index 33b3283..b3657c4 100644
--- a/core/java/android/service/resolver/ResolverTarget.java
+++ b/core/java/android/service/resolver/ResolverTarget.java
@@ -16,13 +16,10 @@
 
 package android.service.resolver;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArrayMap;
-
-import java.util.Map;
 
 /**
  * A ResolverTarget contains features by which an app or option will be ranked, in
@@ -173,6 +170,7 @@
     }
 
     // serialize the class to a string.
+    @NonNull
     @Override
     public String toString() {
         return "ResolverTarget{"
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index bd953ca..94f6a50 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -32,7 +32,6 @@
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
 import android.media.AudioFormat;
 import android.os.AsyncTask;
 import android.os.Handler;
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index dc9c858..619c507 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -229,13 +229,14 @@
             return mHealthCheckTimeoutMillis;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
         }
 
         @Override
-        public boolean equals(Object other) {
+        public boolean equals(@Nullable Object other) {
             if (other == this) {
                 return true;
             }
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 8665106..1c50d73 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -276,6 +276,7 @@
         final int runCount = mDirections.getRunCount();
         for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -360,6 +361,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -417,6 +419,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 07cecd3..74cff20 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -67,20 +67,19 @@
      * found.
      */
     private static android.icu.util.TimeZone getIcuTimeZone(
-            int offset, boolean dst, long when, String country) {
-        if (country == null) {
+            int offsetMillis, boolean isDst, long whenMillis, String countryIso) {
+        if (countryIso == null) {
             return null;
         }
 
         android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
         CountryTimeZones countryTimeZones =
-                TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+                TimeZoneFinder.getInstance().lookupCountryTimeZones(countryIso);
         if (countryTimeZones == null) {
             return null;
         }
-
-        CountryTimeZones.OffsetResult offsetResult =
-                countryTimeZones.lookupByOffsetWithBias(offset, dst, when, bias);
+        CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
+                offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias);
         return offsetResult != null ? offsetResult.mTimeZone : null;
     }
 
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 2efebf6..7e22dd9 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -18,6 +18,7 @@
 
 import static android.view.DisplayInfoProto.APP_HEIGHT;
 import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.FLAGS;
 import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
 import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
 import static android.view.DisplayInfoProto.NAME;
@@ -708,6 +709,7 @@
         protoOutputStream.write(APP_WIDTH, appWidth);
         protoOutputStream.write(APP_HEIGHT, appHeight);
         protoOutputStream.write(NAME, name);
+        protoOutputStream.write(FLAGS, flags);
         protoOutputStream.end(token);
     }
 
diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java
index fcc14c1..fffb323e 100644
--- a/core/java/android/view/GestureExclusionTracker.java
+++ b/core/java/android/view/GestureExclusionTracker.java
@@ -44,7 +44,7 @@
         while (i.hasNext()) {
             final GestureExclusionViewInfo info = i.next();
             final View v = info.getView();
-            if (v == null || !v.isAttachedToWindow() || !v.isShown()) {
+            if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
                 mGestureExclusionViewsChanged = true;
                 i.remove();
                 continue;
@@ -123,7 +123,7 @@
         public int update() {
             final View excludedView = getView();
             if (excludedView == null || !excludedView.isAttachedToWindow()
-                    || !excludedView.isShown()) return GONE;
+                    || !excludedView.isAggregatedVisible()) return GONE;
             final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
             final List<Rect> newRects = new ArrayList<>(localRects.size());
             for (Rect src : localRects) {
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index d2dcb56..f1d152b 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.graphics.Rect;
+
 /**
  * An interface to the PinnedStackController to update it of state changes, and to query
  * information based on the current state.
@@ -30,15 +32,17 @@
     oneway void setIsMinimized(boolean isMinimized);
 
     /**
-     * Notifies the controller of the current min edge size, this is needed to allow the system to
-     * properly calculate the aspect ratio of the expanded PIP.  The given {@param minEdgeSize} is
-     * always bounded to be larger than the default minEdgeSize, so the caller can call this method
-     * with 0 to reset to the default size.
-     */
-    oneway void setMinEdgeSize(int minEdgeSize);
-
-    /**
      * @return what WM considers to be the current device rotation.
      */
     int getDisplayRotation();
+
+    /**
+     * Notifies the controller to actually start the PiP animation.
+     * The bounds would be calculated based on the last save reentry fraction internally.
+     * {@param destinationBounds} is the stack bounds of the final PiP window
+     * and {@param sourceRectHint} is the source bounds hint used when entering picture-in-picture,
+     * expect the same bound passed via IPinnedStackListener#onPrepareAnimation.
+     * {@param animationDuration} suggests the animation duration transitioning to PiP window.
+     */
+    void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
 }
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 2da353b..806d81e 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 
 /**
@@ -36,18 +38,13 @@
     /**
      * Called when the window manager has detected a change that would cause the movement bounds
      * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
-     * the components that allow the listener to calculate the movement bounds itself. The
-     * {@param normalBounds} are also the default bounds that the PiP would be entered in its
-     * current state with the aspect ratio applied.  The {@param animatingBounds} are provided
-     * to indicate the current target bounds of the pinned stack (the final bounds if animating,
-     * the current bounds if not), which may be helpful in calculating dependent animation bounds.
-     *
-     * The {@param displayRotation} is provided so that the client can verify when making certain
-     * calls that it will not provide stale information based on an old display rotation (ie. if
-     * the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
+     * the components that allow the listener to calculate the movement bounds itself.
+     * The {@param animatingBounds} are provided to indicate the current target bounds of the
+     * pinned stack (the final bounds if animating, the current bounds if not),
+     * which may be helpful in calculating dependent animation bounds.
      */
-    void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
+    void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment);
 
     /**
      * Called when window manager decides to adjust the pinned stack bounds because of the IME, or
@@ -76,4 +73,47 @@
      * is first registered to allow the listener to synchronized its state with the controller.
      */
     void onActionsChanged(in ParceledListSlice actions);
+
+    /**
+     * Called by the window manager to notify the listener to save the reentry fraction,
+     * typically when an Activity leaves PiP (picture-in-picture) mode to fullscreen.
+     * {@param componentName} represents the application component of PiP window
+     * while {@param bounds} is the current PiP bounds used to calculate the
+     * reentry snap fraction.
+     */
+    void onSaveReentrySnapFraction(in ComponentName componentName, in Rect bounds);
+
+    /**
+     * Called by the window manager to notify the listener to reset saved reentry fraction,
+     * typically when an Activity enters PiP (picture-in-picture) mode from fullscreen.
+     * {@param componentName} represents the application component of PiP window.
+     */
+    void onResetReentrySnapFraction(in ComponentName componentName);
+
+    /**
+     * Called when the window manager has detected change on DisplayInfo,  or
+     * when the listener is first registered to allow the listener to synchronized its state with
+     * the controller.
+     */
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
+
+    /**
+     * Called by the window manager at the beginning of a configuration update cascade
+     * since the metrics from these resources are used for bounds calculations.
+     */
+    void onConfigurationChanged();
+
+    /**
+     * Called by the window manager when the aspect ratio is reset.
+     */
+    void onAspectRatioChanged(float aspectRatio);
+
+    /**
+     * Called by the window manager to notify the listener to prepare for PiP animation.
+     * Internally, the target bounds would be calculated from the given {@param aspectRatio}
+     * and {@param bounds}, the saved reentry snap fraction also contributes.
+     * Caller would wait for a IPinnedStackController#startAnimation callback to actually
+     * start the animation, see details in IPinnedStackController.
+     */
+    void onPrepareAnimation(in Rect sourceRectHint, float aspectRatio, in Rect bounds);
 }
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index 724d96e..6eb90fc 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -54,6 +54,6 @@
      */
     @UnsupportedAppUsage
     void onAnimationStart(in IRecentsAnimationController controller,
-            in RemoteAnimationTarget[] apps, in Rect homeContentInsets,
-            in Rect minimizedHomeBounds) = 2;
+            in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
+            in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
 }
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 73b023c..7b35aa2 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -34,7 +34,7 @@
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
     @UnsupportedAppUsage
-    void onAnimationStart(in RemoteAnimationTarget[] apps,
+    void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
             in IRemoteAnimationFinishedCallback finishedCallback);
 
     /**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bb9867a..1c32948 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -640,4 +640,14 @@
      * native InputManager before proceeding with tests.
      */
     void syncInputTransactions();
+
+    /**
+     * Returns whether SurfaceFlinger layer tracing is enabled.
+     */
+    boolean isLayerTracing();
+
+    /**
+     * Enables/disables SurfaceFlinger layer tracing.
+     */
+    void setLayerTracing(boolean enabled);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index f73f28a..6ce5ac4 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -31,7 +31,7 @@
 import android.view.InsetsState;
 import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.Transaction;
+import android.view.SurfaceControl.Transaction;
 
 import java.util.List;
 
@@ -156,7 +156,7 @@
      * is null if there is no sync required.
      */
     @UnsupportedAppUsage
-    void finishDrawing(IWindow window, in Transaction postDrawTransaction);
+    void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction);
 
     @UnsupportedAppUsage
     void setInTouchMode(boolean showFocus);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 50ef91f..341c214 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -26,7 +26,6 @@
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.os.UidProto.Sync;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -40,7 +39,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -238,7 +236,12 @@
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
             state.getSource(source.getType()).setFrame(mTmpFrame);
-            surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f, inset != 0));
+
+            // If the system is controlling the insets source, the leash can be null.
+            if (leash != null) {
+                surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+                        null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/, inset != 0));
+            }
         }
     }
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d0ecae9..c798d85 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -602,4 +602,22 @@
             mAnimCallbackScheduled = true;
         }
     }
+
+    @Override
+    public void setSystemBarsAppearance(@Appearance int appearance) {
+        if (mViewRoot.mWindowAttributes.insetsFlags.appearance != appearance) {
+            mViewRoot.mWindowAttributes.insetsFlags.appearance = appearance;
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
+
+    @Override
+    public void setSystemBarsBehavior(@Behavior int behavior) {
+        if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
+            mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior;
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
new file mode 100644
index 0000000..276e80a
--- /dev/null
+++ b/core/java/android/view/InsetsFlags.java
@@ -0,0 +1,67 @@
+/*
+ * 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 android.view;
+
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_SIDE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_TOP_BAR;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_BARS;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
+
+/**
+ * Contains the information about {@link Appearance} and {@link Behavior} of system windows which
+ * can produce insets. This is for carrying the request from a client to the system server.
+ * @hide
+ */
+public class InsetsFlags {
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_OPAQUE_BARS,
+                    equals = APPEARANCE_OPAQUE_BARS,
+                    name = "OPAQUE_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LOW_PROFILE_BARS,
+                    equals = APPEARANCE_LOW_PROFILE_BARS,
+                    name = "LOW_PROFILE_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_TOP_BAR,
+                    equals = APPEARANCE_LIGHT_TOP_BAR,
+                    name = "LIGHT_TOP_BAR"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_SIDE_BARS,
+                    equals = APPEARANCE_LIGHT_SIDE_BARS,
+                    name = "LIGHT_SIDE_BARS")
+    })
+    public @Appearance int appearance;
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_SHOW_BARS_BY_SWIPE,
+                    equals = BEHAVIOR_SHOW_BARS_BY_SWIPE,
+                    name = "SHOW_BARS_BY_SWIPE"),
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    equals = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    name = "SHOW_TRANSIENT_BARS_BY_SWIPE")
+    })
+    public @Behavior int behavior;
+}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 9edccb3..08d45a7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -167,7 +167,7 @@
     }
 
     private void applyHiddenToControl() {
-        if (mSourceControl == null) {
+        if (mSourceControl == null || mSourceControl.getLeash() == null) {
             return;
         }
 
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 4940981..4919074 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Point;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,10 +29,10 @@
 public class InsetsSourceControl implements Parcelable {
 
     private final @InternalInsetType int mType;
-    private final SurfaceControl mLeash;
+    private final @Nullable SurfaceControl mLeash;
     private final Point mSurfacePosition;
 
-    public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash,
+    public InsetsSourceControl(@InternalInsetType int type, @Nullable SurfaceControl leash,
             Point surfacePosition) {
         mType = type;
         mLeash = leash;
@@ -42,7 +43,13 @@
         return mType;
     }
 
-    public SurfaceControl getLeash() {
+    /**
+     * Gets the leash for controlling insets source. If the system is controlling the insets source,
+     * for example, transient bars, the client will receive fake controls without leash in it.
+     *
+     * @return the leash.
+     */
+    public @Nullable SurfaceControl getLeash() {
         return mLeash;
     }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index b2f3f5e..d54e9d5 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -30,6 +30,7 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 
 import com.android.internal.R;
@@ -54,6 +55,7 @@
     private OnClickListener mExpandClickListener;
     private OnClickListener mAppOpsListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+    private LinearLayout mTransferChip;
     private ImageView mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
@@ -116,6 +118,7 @@
         mAppName = findViewById(com.android.internal.R.id.app_name_text);
         mHeaderText = findViewById(com.android.internal.R.id.header_text);
         mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary);
+        mTransferChip = findViewById(com.android.internal.R.id.media_seamless);
         mExpandButton = findViewById(com.android.internal.R.id.expand_button);
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
@@ -148,9 +151,11 @@
             int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
                     lp.topMargin + lp.bottomMargin, lp.height);
             child.measure(childWidthSpec, childHeightSpec);
+            // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps) {
+                    || child == mAppOps
+                    || child == mTransferChip) {
                 iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
             } else {
                 totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -211,9 +216,11 @@
             int layoutRight;
             int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
             int bottom = top + childHeight;
+            // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps) {
+                    || child == mAppOps
+                    || child == mTransferChip) {
                 if (end == getMeasuredWidth()) {
                     layoutRight = end - mContentEndMargin;
                 } else {
diff --git a/core/java/android/view/SurfaceControl.aidl b/core/java/android/view/SurfaceControl.aidl
index 744ead2..6c51ae9 100644
--- a/core/java/android/view/SurfaceControl.aidl
+++ b/core/java/android/view/SurfaceControl.aidl
@@ -17,3 +17,4 @@
 package android.view;
 
 parcelable SurfaceControl;
+parcelable SurfaceControl.Transaction;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 522ad64..d5559aa 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -105,8 +105,6 @@
             long relativeToObject, int zorder);
     private static native void nativeSetPosition(long transactionObj, long nativeObject,
             float x, float y);
-    private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
-            long nativeObject);
     private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long transactionObj,
             long nativeObject, Region region);
@@ -1104,16 +1102,6 @@
     /**
      * @hide
      */
-    public void setGeometryAppliesWithResize() {
-        checkNotReleased();
-        synchronized(SurfaceControl.class) {
-            sGlobalTransaction.setGeometryAppliesWithResize(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
     public void setBufferSize(int w, int h) {
         checkNotReleased();
         synchronized(SurfaceControl.class) {
@@ -2488,20 +2476,6 @@
         }
 
         /**
-         * If the buffer size changes in this transaction, position and crop updates specified
-         * in this transaction will not complete until a buffer of the new size
-         * arrives. As transform matrix and size are already frozen in this fashion,
-         * this enables totally freezing the surface until the resize has completed
-         * (at which point the geometry influencing aspects of this transaction will then occur)
-         * @hide
-         */
-        public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
-            sc.checkNotReleased();
-            nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
-            return this;
-        }
-
-        /**
          * Sets the security of the surface.  Setting the flag is equivalent to creating the
          * Surface with the {@link #SECURE} flag.
          * @hide
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 90e69f3..262b9e5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -136,6 +136,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     boolean mIsCreating = false;
     private volatile boolean mRtHandlingPositionUpdates = false;
+    private volatile boolean mRtReleaseSurfaces = false;
 
     private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener =
             this::updateSurface;
@@ -658,7 +659,14 @@
     }
 
     private void releaseSurfaces() {
+        mSurfaceAlpha = 1f;
+
         synchronized (mSurfaceControlLock) {
+            if (mRtHandlingPositionUpdates) {
+                mRtReleaseSurfaces = true;
+                return;
+            }
+
             if (mSurfaceControl != null) {
                 mTmpTransaction.remove(mSurfaceControl);
                 mSurfaceControl = null;
@@ -669,7 +677,6 @@
             }
             mTmpTransaction.apply();
         }
-        mSurfaceAlpha = 1f;
     }
 
     /** @hide */
@@ -1084,7 +1091,9 @@
             // the synchronization would violate the rule that RT must never block
             // on the UI thread which would open up potential deadlocks. The risk of
             // a single-frame desync is therefore preferable for now.
-            mRtHandlingPositionUpdates = true;
+            synchronized(mSurfaceControlLock) {
+                mRtHandlingPositionUpdates = true;
+            }
             if (mRTLastReportedPosition.left == left
                     && mRTLastReportedPosition.top == top
                     && mRTLastReportedPosition.right == right
@@ -1126,6 +1135,18 @@
                         frameNumber);
             }
             mRtTransaction.hide(mSurfaceControl);
+
+            synchronized (mSurfaceControlLock) {
+                if (mRtReleaseSurfaces) {
+                    mRtReleaseSurfaces = false;
+                    mRtTransaction.remove(mSurfaceControl);
+                    mRtTransaction.remove(mBackgroundControl);
+                    mSurfaceControl = null;
+                    mBackgroundControl = null;
+                }
+                mRtHandlingPositionUpdates = false;
+            }
+
             mRtTransaction.apply();
         }
     };
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 85457cb..a6536f5d 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -30,6 +30,14 @@
  */
 public class SyncRtSurfaceTransactionApplier {
 
+    public static final int FLAG_ALL = 0xffffffff;
+    public static final int FLAG_ALPHA = 1;
+    public static final int FLAG_MATRIX = 1 << 1;
+    public static final int FLAG_WINDOW_CROP = 1 << 2;
+    public static final int FLAG_LAYER = 1 << 3;
+    public static final int FLAG_CORNER_RADIUS = 1 << 4;
+    public static final int FLAG_VISIBILITY = 1 << 5;
+
     private final Surface mTargetSurface;
     private final ViewRootImpl mTargetViewRootImpl;
     private final float[] mTmpFloat9 = new float[9];
@@ -72,15 +80,27 @@
     }
 
     public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) {
-        t.setMatrix(params.surface, params.matrix, tmpFloat9);
-        t.setWindowCrop(params.surface, params.windowCrop);
-        t.setAlpha(params.surface, params.alpha);
-        t.setLayer(params.surface, params.layer);
-        t.setCornerRadius(params.surface, params.cornerRadius);
-        if (params.visible) {
-            t.show(params.surface);
-        } else {
-            t.hide(params.surface);
+        if ((params.flags & FLAG_MATRIX) != 0) {
+            t.setMatrix(params.surface, params.matrix, tmpFloat9);
+        }
+        if ((params.flags & FLAG_WINDOW_CROP) != 0) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
+        if ((params.flags & FLAG_ALPHA) != 0) {
+            t.setAlpha(params.surface, params.alpha);
+        }
+        if ((params.flags & FLAG_LAYER) != 0) {
+            t.setLayer(params.surface, params.layer);
+        }
+        if ((params.flags & FLAG_CORNER_RADIUS) != 0) {
+            t.setCornerRadius(params.surface, params.cornerRadius);
+        }
+        if ((params.flags & FLAG_VISIBILITY) != 0) {
+            if (params.visible) {
+                t.show(params.surface);
+            } else {
+                t.hide(params.surface);
+            }
         }
     }
 
@@ -115,17 +135,95 @@
 
     public static class SurfaceParams {
 
-        /**
-         * Constructs surface parameters to be applied when the current view state gets pushed to
-         * RenderThread.
-         *
-         * @param surface The surface to modify.
-         * @param alpha Alpha to apply.
-         * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
-         */
-        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+        public static class Builder {
+            final SurfaceControl surface;
+            int flags;
+            float alpha;
+            float cornerRadius;
+            Matrix matrix;
+            Rect windowCrop;
+            int layer;
+            boolean visible;
+
+            /**
+             * @param surface The surface to modify.
+             */
+            public Builder(SurfaceControl surface) {
+                this.surface = surface;
+            }
+
+            /**
+             * @param alpha The alpha value to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withAlpha(float alpha) {
+                this.alpha = alpha;
+                flags |= FLAG_ALPHA;
+                return this;
+            }
+
+            /**
+             * @param matrix The matrix to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withMatrix(Matrix matrix) {
+                this.matrix = matrix;
+                flags |= FLAG_MATRIX;
+                return this;
+            }
+
+            /**
+             * @param windowCrop The window crop to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withWindowCrop(Rect windowCrop) {
+                this.windowCrop = windowCrop;
+                flags |= FLAG_WINDOW_CROP;
+                return this;
+            }
+
+            /**
+             * @param layer The layer to assign the surface.
+             * @return this Builder
+             */
+            public Builder withLayer(int layer) {
+                this.layer = layer;
+                flags |= FLAG_LAYER;
+                return this;
+            }
+
+            /**
+             * @param radius the Radius for rounded corners to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withCornerRadius(float radius) {
+                this.cornerRadius = radius;
+                flags |= FLAG_CORNER_RADIUS;
+                return this;
+            }
+
+            /**
+             * @param visible The visibility to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withVisibility(boolean visible) {
+                this.visible = visible;
+                flags |= FLAG_VISIBILITY;
+                return this;
+            }
+
+            /**
+             * @return a new SurfaceParams instance
+             */
+            public SurfaceParams build() {
+                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+                        cornerRadius, visible);
+            }
+        }
+
+        private SurfaceParams(SurfaceControl surface, int params, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = params;
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
@@ -135,6 +233,36 @@
             this.visible = visible;
         }
 
+
+        /**
+         * Constructs surface parameters to be applied when the current view state gets pushed to
+         * RenderThread.
+         *
+         * @param surface The surface to modify.
+         * @param alpha Alpha to apply.
+         * @param matrix Matrix to apply.
+         * @param windowCrop Crop to apply.
+         * @param layer The layer to apply.
+         * @param cornerRadius The corner radius to apply.
+         * @param visible The visibility to apply.
+         *
+         * @deprecated Use {@link SurfaceParams.Builder} to create an instance.
+         */
+        @Deprecated
+        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+                Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = FLAG_ALL;
+            this.surface = surface;
+            this.alpha = alpha;
+            this.matrix = new Matrix(matrix);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
+            this.layer = layer;
+            this.cornerRadius = cornerRadius;
+            this.visible = visible;
+        }
+
+        private final int flags;
+
         @VisibleForTesting
         public final SurfaceControl surface;
 
diff --git a/core/java/android/view/Transaction.aidl b/core/java/android/view/Transaction.aidl
deleted file mode 100644
index 8d24b43..0000000
--- a/core/java/android/view/Transaction.aidl
+++ /dev/null
@@ -1,19 +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 android.view.SurfaceControl;
-
-parcelable Transaction;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6ce7120..cfb6a79a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4356,6 +4356,11 @@
     private Insets mLayoutInsets;
 
     /**
+     * Briefly describes the state of the view and is primarily used for accessibility support.
+     */
+    private CharSequence mStateDescription;
+
+    /**
      * Briefly describes the view and is primarily used for accessibility support.
      */
     private CharSequence mContentDescription;
@@ -5125,6 +5130,11 @@
     @Nullable
     private ContentCaptureSession mContentCaptureSession;
 
+    /**
+     * Whether {@link ContentCaptureSession} is cached, resets on {@link #invalidate()}.
+     */
+    private boolean mContentCaptureSessionCached;
+
     @LayoutRes
     private int mSourceLayoutId = ID_NULL;
 
@@ -5138,11 +5148,6 @@
     private int mExplicitStyle;
 
     /**
-     * Cached reference to the {@link ContentCaptureSession}, is reset on {@link #invalidate()}.
-     */
-    private ContentCaptureSession mCachedContentCaptureSession;
-
-    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -9560,18 +9565,21 @@
      */
     @Nullable
     public final ContentCaptureSession getContentCaptureSession() {
-        if (mCachedContentCaptureSession != null) {
-            return mCachedContentCaptureSession;
+        if (mContentCaptureSessionCached) {
+            return mContentCaptureSession;
         }
 
-        mCachedContentCaptureSession = getAndCacheContentCaptureSession();
-        return mCachedContentCaptureSession;
+        mContentCaptureSession = getAndCacheContentCaptureSession();
+        mContentCaptureSessionCached = true;
+        return mContentCaptureSession;
     }
 
     @Nullable
     private ContentCaptureSession getAndCacheContentCaptureSession() {
         // First try the session explicitly set by setContentCaptureSession()
-        if (mContentCaptureSession != null) return mContentCaptureSession;
+        if (mContentCaptureSession != null) {
+            return mContentCaptureSession;
+        }
 
         // Then the session explicitly set in an ancestor
         ContentCaptureSession session = null;
@@ -9922,6 +9930,7 @@
         info.setImportantForAccessibility(isImportantForAccessibility());
         info.setPackageName(mContext.getPackageName());
         info.setClassName(getAccessibilityClassName());
+        info.setStateDescription(getStateDescription());
         info.setContentDescription(getContentDescription());
 
         info.setEnabled(isEnabled());
@@ -10294,6 +10303,22 @@
     }
 
     /**
+     * Returns the {@link View}'s state description.
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the state description presented to accessibility services.
+     * You must call {@link #setStateDescription(CharSequence)} to modify the
+     * state description.
+     *
+     * @return the state description
+     * @see #setStateDescription(CharSequence)
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    public final @Nullable CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
      * Returns the {@link View}'s content description.
      * <p>
      * <strong>Note:</strong> Do not override this method, as it will have no
@@ -10312,6 +10337,36 @@
     }
 
     /**
+     * Sets the {@link View}'s state description.
+     * <p>
+     * A state description briefly describes the states of the view and is primarily used
+     * for accessibility support to determine how the states of a view should be presented to
+     * the user. It is a supplement to the boolean states (for example, checked/unchecked) and
+     * it is used for customized state description (for example, "wifi, connected, three bars").
+     * State description changes frequently while content description should change less often.
+     *
+     * @param stateDescription The state description.
+     * @see #getStateDescription()
+     */
+    @RemotableViewMethod
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        if (mStateDescription == null) {
+            if (stateDescription == null) {
+                return;
+            }
+        } else if (mStateDescription.equals(stateDescription)) {
+            return;
+        }
+        mStateDescription = stateDescription;
+        if (!TextUtils.isEmpty(stateDescription)
+                && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
+    }
+
+    /**
      * Sets the {@link View}'s content description.
      * <p>
      * A content description briefly describes the view and is primarily used
@@ -14324,6 +14379,14 @@
     }
 
     /**
+     * @return true if this view and all ancestors are visible as of the last
+     * {@link #onVisibilityAggregated(boolean)} call.
+     */
+    boolean isAggregatedVisible() {
+        return (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+    }
+
+    /**
      * Internal dispatching method for {@link #onVisibilityAggregated}. Overridden by
      * ViewGroup. Intended to only be called when {@link #isAttachedToWindow()},
      * {@link #getWindowVisibility()} is {@link #VISIBLE} and this view's parent {@link #isShown()}.
@@ -14351,7 +14414,7 @@
     @CallSuper
     public void onVisibilityAggregated(boolean isVisible) {
         // Update our internal visibility tracking so we can detect changes
-        boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+        boolean oldVisible = isAggregatedVisible();
         mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE)
                 : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE);
         if (isVisible && mAttachInfo != null) {
@@ -14403,7 +14466,9 @@
         }
 
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-        updateSystemGestureExclusionRects();
+        if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
+            postUpdateSystemGestureExclusionRects();
+        }
     }
 
     /**
@@ -18084,7 +18149,7 @@
 
         // Reset content capture caches
         mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
-        mCachedContentCaptureSession = null;
+        mContentCaptureSessionCached = false;
 
         if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index feba7bb..f25206d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -408,6 +408,7 @@
      *            or more of:
      *            <ul>
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 957673d..859e9a4 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -1138,6 +1138,12 @@
 
             boolean hardwareAccelerated = mView.isHardwareAccelerated();
 
+            // alpha requires slightly different treatment than the other (transform) properties.
+            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+            // logic is dependent on how the view handles an internal call to onSetAlpha().
+            // We track what kinds of properties are set, and how alpha is handled when it is
+            // set, and perform the invalidation steps appropriately.
+            boolean alphaHandled = false;
             if (!hardwareAccelerated) {
                 mView.invalidateParentCaches();
             }
@@ -1152,7 +1158,11 @@
                 for (int i = 0; i < count; ++i) {
                     NameValuesHolder values = valueList.get(i);
                     float value = values.mFromValue + fraction * values.mDeltaValue;
-                    setValue(values.mNameConstant, value);
+                    if (values.mNameConstant == ALPHA) {
+                        alphaHandled = mView.setAlphaNoInvalidation(value);
+                    } else {
+                        setValue(values.mNameConstant, value);
+                    }
                 }
             }
             if ((propertyMask & TRANSFORM_MASK) != 0) {
@@ -1160,8 +1170,13 @@
                     mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
                 }
             }
-
-            mView.invalidateViewProperty(false, false);
+            // invalidate(false) in all cases except if alphaHandled gets set to true
+            // via the call to setAlphaNoInvalidation(), above
+            if (alphaHandled) {
+                mView.invalidate(true);
+            } else {
+                mView.invalidateViewProperty(false, false);
+            }
             if (mUpdateListener != null) {
                 mUpdateListener.onAnimationUpdate(animation);
             }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3a51eaa..3756a7d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -468,7 +468,6 @@
     private final UnhandledKeyManager mUnhandledKeyManager = new UnhandledKeyManager();
 
     boolean mWindowAttributesChanged = false;
-    int mWindowAttributesChangesFlag = 0;
 
     // These can be accessed by any thread, must be protected with a lock.
     // Surface can never be reassigned or cleared (use Surface.clear()).
@@ -865,7 +864,6 @@
 
                 mSoftInputMode = attrs.softInputMode;
                 mWindowAttributesChanged = true;
-                mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
                 mAttachInfo.mRootView = view;
                 mAttachInfo.mScalingRequired = mTranslator != null;
                 mAttachInfo.mApplicationScale =
@@ -1269,14 +1267,12 @@
             attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
             attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
 
-            mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
-            if ((mWindowAttributesChangesFlag
-                    & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
+            final int changes = mWindowAttributes.copyFrom(attrs);
+            if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
                 // Recompute system ui visibility.
                 mAttachInfo.mRecomputeGlobalAttributes = true;
             }
-            if ((mWindowAttributesChangesFlag
-                    & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+            if ((changes & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
                 // Request to update light center.
                 mAttachInfo.mNeedsUpdateLightCenter = true;
             }
@@ -2001,7 +1997,6 @@
         mIsInTraversal = true;
         mWillDrawSoon = true;
         boolean windowSizeMayChange = false;
-        final boolean windowAttributesChanged = mWindowAttributesChanged;
         WindowManager.LayoutParams lp = mWindowAttributes;
 
         int desiredWindowWidth;
@@ -2018,10 +2013,6 @@
                 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
 
         WindowManager.LayoutParams params = null;
-        if (mWindowAttributesChanged) {
-            mWindowAttributesChanged = false;
-            params = lp;
-        }
         CompatibilityInfo compatibilityInfo =
                 mDisplay.getDisplayAdjustments().getCompatibilityInfo();
         if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
@@ -2037,8 +2028,6 @@
             }
         }
 
-        mWindowAttributesChangesFlag = 0;
-
         Rect frame = mWinFrame;
         if (mFirst) {
             mFullRedrawNeeded = true;
@@ -2200,16 +2189,6 @@
             }
         }
 
-        if (params != null) {
-            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
-                if (!PixelFormat.formatHasAlpha(params.format)) {
-                    params.format = PixelFormat.TRANSLUCENT;
-                }
-            }
-            mAttachInfo.mOverscanRequested = (params.flags
-                    & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
-        }
-
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
             mLastOverscanRequested = mAttachInfo.mOverscanRequested;
@@ -2265,6 +2244,21 @@
         /* True if surface generation id changes. */
         boolean surfaceReplaced = false;
 
+        final boolean windowAttributesChanged = mWindowAttributesChanged;
+        if (windowAttributesChanged) {
+            mWindowAttributesChanged = false;
+            params = lp;
+        }
+
+        if (params != null) {
+            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
+                    && !PixelFormat.formatHasAlpha(params.format)) {
+                params.format = PixelFormat.TRANSLUCENT;
+            }
+            mAttachInfo.mOverscanRequested =
+                    (params.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
+        }
+
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
             mForceNextWindowRelayout = false;
@@ -3313,14 +3307,19 @@
     public void requestTransparentRegion(View child) {
         // the test below should not fail unless someone is messing with us
         checkThread();
-        if (mView == child) {
+        if (mView != child) {
+            return;
+        }
+
+        if ((mView.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
             mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
             // Need to make sure we re-evaluate the window attributes next
             // time around, to ensure the window has the correct format.
             mWindowAttributesChanged = true;
-            mWindowAttributesChangesFlag = 0;
-            requestLayout();
         }
+
+        // Always request layout to apply the latest transparent region.
+        requestLayout();
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index b708323..396422e 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -18,9 +18,14 @@
 
 import static android.view.WindowInsets.Type.ime;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetType;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Interface to control windows that generate insets.
  *
@@ -30,6 +35,73 @@
 public interface WindowInsetsController {
 
     /**
+     * Makes system bars become opaque with solid dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_OPAQUE_BARS = 1;
+
+    /**
+     * Makes items on system bars become less noticeable without changing the layout of the bars.
+     * @hide
+     */
+    int APPEARANCE_LOW_PROFILE_BARS = 1 << 1;
+
+    /**
+     * Changes the foreground color for the light top bar so that the items on the bar can be read
+     * clearly.
+     */
+    int APPEARANCE_LIGHT_TOP_BAR = 1 << 2;
+
+    /**
+     * Changes the foreground color for the light side bars so that the items on the bar can be read
+     * clearly.
+     */
+    int APPEARANCE_LIGHT_SIDE_BARS = 1 << 3;
+
+    /** Determines the appearance of system bars. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {APPEARANCE_OPAQUE_BARS, APPEARANCE_LOW_PROFILE_BARS,
+            APPEARANCE_LIGHT_TOP_BAR, APPEARANCE_LIGHT_SIDE_BARS})
+    @interface Appearance {
+    }
+
+    /**
+     * The default option for {@link #setSystemBarsBehavior(int)}. The side bars will be forcibly
+     * shown by the system on any user interaction on the corresponding display if the side bars are
+     * hidden by {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     */
+    int BEHAVIOR_SHOW_SIDE_BARS_BY_TOUCH = 0;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding the side bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
+     * as swiping from the edge of the screen where the bar is hidden from.</p>
+     */
+    int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding the side bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed temporarily with system
+     * gestures, such as swiping from the edge of the screen where the bar is hidden from. These
+     * transient system bars will overlay app’s content, may have some degree of transparency, and
+     * will automatically hide after a short timeout.</p>
+     */
+    int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
+
+    /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {BEHAVIOR_SHOW_SIDE_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
+            BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
+    @interface Behavior {
+    }
+
+    /**
      * Makes a set of windows that cause insets appear on screen.
      * <p>
      * Note that if the window currently doesn't have control over a certain type, it will apply the
@@ -108,4 +180,20 @@
     default void hideInputMethod() {
         hide(ime());
     }
+
+    /**
+     * Controls the appearance of system bars.
+     *
+     * @param appearance Bitmask of {@link Appearance} flags.
+     * @see Appearance
+     */
+    void setSystemBarsAppearance(@Appearance int appearance);
+
+    /**
+     * Controls the behavior of system bars.
+     *
+     * @param behavior Determines how the bars behave when being hidden by the application.
+     * @see Behavior
+     */
+    void setSystemBarsBehavior(@Behavior int behavior);
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2e5a750..001ab66 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
 import static android.view.WindowLayoutParamsProto.ALPHA;
+import static android.view.WindowLayoutParamsProto.APPEARANCE;
+import static android.view.WindowLayoutParamsProto.BEHAVIOR;
 import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
 import static android.view.WindowLayoutParamsProto.COLOR_MODE;
 import static android.view.WindowLayoutParamsProto.FLAGS;
@@ -1473,6 +1475,9 @@
          * <p>When this flag is enabled for a window, it automatically sets
          * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
          * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag may be ignored.
          */
         public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
 
@@ -1492,6 +1497,10 @@
          * <p>When this flag is enabled for a window, it automatically sets
          * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
          * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag can be disabled
+         * by the car manufacturers.
          */
         public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
 
@@ -2612,6 +2621,14 @@
         @ActivityInfo.ColorMode
         private int mColorMode = COLOR_MODE_DEFAULT;
 
+        /**
+         * Carries the requests about {@link WindowInsetsController.Appearance} and
+         * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
+         *
+         * @hide
+         */
+        public final InsetsFlags insetsFlags = new InsetsFlags();
+
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
             type = TYPE_APPLICATION;
@@ -2774,6 +2791,8 @@
             TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
             out.writeInt(mColorMode);
             out.writeLong(hideTimeoutMilliseconds);
+            out.writeInt(insetsFlags.appearance);
+            out.writeInt(insetsFlags.behavior);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -2830,6 +2849,8 @@
             accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             mColorMode = in.readInt();
             hideTimeoutMilliseconds = in.readLong();
+            insetsFlags.appearance = in.readInt();
+            insetsFlags.behavior = in.readInt();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2876,7 +2897,7 @@
         /** {@hide} */
         public static final int COLOR_MODE_CHANGED = 1 << 26;
         /** {@hide} */
-        public static final int EVERYTHING_CHANGED = 0xffffffff;
+        public static final int INSET_FLAGS_CHANGED = 1 << 27;
 
         // internal buffer to backup/restore parameters under compatibility mode.
         private int[] mCompatibilityParamsBackup = null;
@@ -3065,6 +3086,16 @@
             // This can't change, it's only set at window creation time.
             hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
 
+            if (insetsFlags.appearance != o.insetsFlags.appearance) {
+                insetsFlags.appearance = o.insetsFlags.appearance;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
+            if (insetsFlags.behavior != o.insetsFlags.behavior) {
+                insetsFlags.behavior = o.insetsFlags.behavior;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
             return changes;
         }
 
@@ -3212,6 +3243,17 @@
                 sb.append(prefix).append("  vsysui=").append(ViewDebug.flagsToString(
                         View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
             }
+            if (insetsFlags.appearance != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  apr=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "appearance", insetsFlags.appearance));
+            }
+            if (insetsFlags.behavior != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  bhv=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "behavior", insetsFlags.behavior));
+            }
+
             sb.append('}');
             return sb.toString();
         }
@@ -3247,6 +3289,8 @@
             proto.write(PRIVATE_FLAGS, privateFlags);
             proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
             proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
+            proto.write(APPEARANCE, insetsFlags.appearance);
+            proto.write(BEHAVIOR, insetsFlags.behavior);
             proto.end(token);
         }
 
diff --git a/core/java/android/view/WindowlessViewRoot.java b/core/java/android/view/WindowlessViewRoot.java
index 75057a2..b76e1fa 100644
--- a/core/java/android/view/WindowlessViewRoot.java
+++ b/core/java/android/view/WindowlessViewRoot.java
@@ -21,12 +21,15 @@
 import android.view.SurfaceControl;
 import android.view.View;
 
+import android.annotation.TestApi;
+
 /**
  * Utility class for adding a view hierarchy to a SurfaceControl.
  *
  * See WindowlessWmTest for example usage.
  * @hide
  */
+@TestApi
 public class WindowlessViewRoot {
     ViewRootImpl mViewRoot;
     WindowlessWindowManager mWm;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 5844365..430fb6d 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.res.Configuration;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -93,6 +94,15 @@
     @Override
     public void remove(android.view.IWindow window) {}
 
+    private boolean isOpaque(WindowManager.LayoutParams attrs) {
+        if (attrs.surfaceInsets != null && attrs.surfaceInsets.left != 0 || 
+                attrs.surfaceInsets.top != 0 || attrs.surfaceInsets.right != 0 ||
+                attrs.surfaceInsets.bottom != 0) {
+            return false;
+        }
+        return !PixelFormat.formatHasAlpha(attrs.format);
+    }
+
     @Override
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
@@ -109,7 +119,17 @@
                     "Invalid window token (never added or removed already)");
         }
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        t.show(sc).setBufferSize(sc, requestedWidth, requestedHeight).apply();
+
+        final Rect surfaceInsets = attrs.surfaceInsets;
+        int width = surfaceInsets != null ?
+                requestedWidth + surfaceInsets.left + surfaceInsets.right : requestedWidth;
+        int height = surfaceInsets != null ?
+                requestedHeight + surfaceInsets.top + surfaceInsets.bottom : requestedHeight;
+
+        t.show(sc)
+            .setBufferSize(sc, width, height)
+            .setOpaque(sc, isOpaque(attrs))
+            .apply();
         outSurfaceControl.copyFrom(sc);
         outFrame.set(0, 0, requestedWidth, requestedHeight);
 
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 484d9a1..dc8bf9b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -71,7 +71,9 @@
 
     private boolean mIsAllWindowsCached;
 
-    private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+    // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
+    // The key of outer SparseArray is display ID and the key of inner SparseArray is window ID.
+    private final SparseArray<SparseArray<AccessibilityWindowInfo>> mWindowCacheByDisplay =
             new SparseArray<>();
 
     private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
@@ -84,34 +86,66 @@
         mAccessibilityNodeRefresher = nodeRefresher;
     }
 
-    public void setWindows(List<AccessibilityWindowInfo> windows) {
+    /**
+     * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
+     * The key of SparseArray is display ID.
+     *
+     * @param windowsOnAllDisplays The accessibility windows of all displays.
+     */
+    public void setWindowsOnAllDisplays(
+            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
         synchronized (mLock) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "Set windows");
             }
-            clearWindowCache();
-            if (windows == null) {
+            clearWindowCacheLocked();
+            if (windowsOnAllDisplays == null) {
                 return;
             }
-            final int windowCount = windows.size();
-            for (int i = 0; i < windowCount; i++) {
-                final AccessibilityWindowInfo window = windows.get(i);
-                addWindow(window);
+
+            final int displayCounts = windowsOnAllDisplays.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final List<AccessibilityWindowInfo> windowsOfDisplay =
+                        windowsOnAllDisplays.valueAt(i);
+
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                final int displayId = windowsOnAllDisplays.keyAt(i);
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    addWindowByDisplayLocked(displayId, windowsOfDisplay.get(j));
+                }
             }
             mIsAllWindowsCached = true;
         }
     }
 
+    /**
+     * Sets an {@link AccessibilityWindowInfo} into the cache.
+     *
+     * @param window The accessibility window.
+     */
     public void addWindow(AccessibilityWindowInfo window) {
         synchronized (mLock) {
             if (DEBUG) {
-                Log.i(LOG_TAG, "Caching window: " + window.getId());
+                Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+                        + window.getDisplayId() + " ]");
             }
-            final int windowId = window.getId();
-            mWindowCache.put(windowId, new AccessibilityWindowInfo(window));
+            addWindowByDisplayLocked(window.getDisplayId(), window);
         }
     }
 
+    private void addWindowByDisplayLocked(int displayId, AccessibilityWindowInfo window) {
+        SparseArray<AccessibilityWindowInfo> windows = mWindowCacheByDisplay.get(displayId);
+        if (windows == null) {
+            windows = new SparseArray<>();
+            mWindowCacheByDisplay.put(displayId, windows);
+        }
+        final int windowId = window.getId();
+        windows.put(windowId, new AccessibilityWindowInfo(window));
+    }
     /**
      * Notifies the cache that the something in the UI changed. As a result
      * the cache will either refresh some nodes or evict some nodes.
@@ -236,44 +270,82 @@
         }
     }
 
-    public List<AccessibilityWindowInfo> getWindows() {
+    /**
+     * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
+     *
+     * @return All cached {@link AccessibilityWindowInfo}s of all displays
+     *         or null if such not found. The key of SparseArray is display ID.
+     */
+    public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
         synchronized (mLock) {
             if (!mIsAllWindowsCached) {
                 return null;
             }
+            final SparseArray<List<AccessibilityWindowInfo>> returnWindows = new SparseArray<>();
+            final int displayCounts = mWindowCacheByDisplay.size();
 
-            final int windowCount = mWindowCache.size();
-            if (windowCount > 0) {
-                // Careful to return the windows in a decreasing layer order.
-                SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
-                sortedWindows.clear();
+            if (displayCounts > 0) {
+                for (int i = 0; i < displayCounts; i++) {
+                    final int displayId = mWindowCacheByDisplay.keyAt(i);
+                    final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                            mWindowCacheByDisplay.valueAt(i);
 
-                for (int i = 0; i < windowCount; i++) {
-                    AccessibilityWindowInfo window = mWindowCache.valueAt(i);
-                    sortedWindows.put(window.getLayer(), window);
+                    if (windowsOfDisplay == null) {
+                        continue;
+                    }
+
+                    final int windowCount = windowsOfDisplay.size();
+                    if (windowCount > 0) {
+                        // Careful to return the windows in a decreasing layer order.
+                        SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+                        sortedWindows.clear();
+
+                        for (int j = 0; j < windowCount; j++) {
+                            AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+                            sortedWindows.put(window.getLayer(), window);
+                        }
+
+                        // It's possible in transient conditions for two windows to share the same
+                        // layer, which results in sortedWindows being smaller than
+                        // mWindowCacheByDisplay
+                        final int sortedWindowCount = sortedWindows.size();
+                        List<AccessibilityWindowInfo> windows =
+                                new ArrayList<>(sortedWindowCount);
+                        for (int j = sortedWindowCount - 1; j >= 0; j--) {
+                            AccessibilityWindowInfo window = sortedWindows.valueAt(j);
+                            windows.add(new AccessibilityWindowInfo(window));
+                            sortedWindows.removeAt(j);
+                        }
+                        returnWindows.put(displayId, windows);
+                    }
                 }
-
-                // It's possible in transient conditions for two windows to share the same
-                // layer, which results in sortedWindows being smaller than mWindowCache
-                final int sortedWindowCount = sortedWindows.size();
-                List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
-                for (int i = sortedWindowCount - 1; i >= 0; i--) {
-                    AccessibilityWindowInfo window = sortedWindows.valueAt(i);
-                    windows.add(new AccessibilityWindowInfo(window));
-                    sortedWindows.removeAt(i);
-                }
-
-                return windows;
+                return returnWindows;
             }
             return null;
         }
     }
 
+    /**
+     * Gets an {@link AccessibilityWindowInfo} by windowId.
+     *
+     * @param windowId The id of the window.
+     *
+     * @return The {@link AccessibilityWindowInfo} or null if such not found.
+     */
     public AccessibilityWindowInfo getWindow(int windowId) {
         synchronized (mLock) {
-            AccessibilityWindowInfo window = mWindowCache.get(windowId);
-            if (window != null) {
-                return new AccessibilityWindowInfo(window);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+                if (window != null) {
+                    return new AccessibilityWindowInfo(window);
+                }
             }
             return null;
         }
@@ -358,7 +430,7 @@
             if (DEBUG) {
                 Log.i(LOG_TAG, "clear()");
             }
-            clearWindowCache();
+            clearWindowCacheLocked();
             final int nodesForWindowCount = mNodeCache.size();
             for (int i = nodesForWindowCount - 1; i >= 0; i--) {
                 final int windowId = mNodeCache.keyAt(i);
@@ -370,8 +442,23 @@
         }
     }
 
-    private void clearWindowCache() {
-        mWindowCache.clear();
+    private void clearWindowCacheLocked() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "clearWindowCacheLocked");
+        }
+        final int displayCounts = mWindowCacheByDisplay.size();
+
+        if (displayCounts > 0) {
+            for (int i = displayCounts - 1; i >= 0; i--) {
+                final int displayId = mWindowCacheByDisplay.keyAt(i);
+                final SparseArray<AccessibilityWindowInfo> windows =
+                        mWindowCacheByDisplay.get(displayId);
+                if (windows != null) {
+                    windows.clear();
+                }
+                mWindowCacheByDisplay.remove(displayId);
+            }
+        }
         mIsAllWindowsCached = false;
     }
 
@@ -444,32 +531,41 @@
     public void checkIntegrity() {
         synchronized (mLock) {
             // Get the root.
-            if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+            if (mWindowCacheByDisplay.size() <= 0 && mNodeCache.size() == 0) {
                 return;
             }
 
             AccessibilityWindowInfo focusedWindow = null;
             AccessibilityWindowInfo activeWindow = null;
 
-            final int windowCount = mWindowCache.size();
-            for (int i = 0; i < windowCount; i++) {
-                AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
 
-                // Check for one active window.
-                if (window.isActive()) {
-                    if (activeWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate active window:" + window);
-                    } else {
-                        activeWindow = window;
-                    }
+                if (windowsOfDisplay == null) {
+                    continue;
                 }
 
-                // Check for one focused window.
-                if (window.isFocused()) {
-                    if (focusedWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate focused window:" + window);
-                    } else {
-                        focusedWindow = window;
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    final AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+
+                    // Check for one active window.
+                    if (window.isActive()) {
+                        if (activeWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate active window:" + window);
+                        } else {
+                            activeWindow = window;
+                        }
+                    }
+                    // Check for one focused window.
+                    if (window.isFocused()) {
+                        if (focusedWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate focused window:" + window);
+                        } else {
+                            focusedWindow = window;
+                        }
                     }
                 }
             }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index fd09a87..32b0f41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -602,6 +602,13 @@
     public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
 
     /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * state description of the node as returned by
+     * {@link AccessibilityNodeInfo#getStateDescription} changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040;
+
+    /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
@@ -696,6 +703,7 @@
                     CONTENT_CHANGE_TYPE_SUBTREE,
                     CONTENT_CHANGE_TYPE_TEXT,
                     CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+                    CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
                     CONTENT_CHANGE_TYPE_PANE_TITLE,
                     CONTENT_CHANGE_TYPE_PANE_APPEARED,
                     CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
@@ -885,6 +893,7 @@
      * @return The bit mask of change types. One or more of:
      *         <ul>
      *         <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *         <li>{@link #CONTENT_CHANGE_TYPE_SUBTREE}
      *         <li>{@link #CONTENT_CHANGE_TYPE_TEXT}
      *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_TITLE}
@@ -906,6 +915,8 @@
         switch (type) {
             case CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION:
                 return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION";
+            case CONTENT_CHANGE_TYPE_STATE_DESCRIPTION:
+                return "CONTENT_CHANGE_TYPE_STATE_DESCRIPTION";
             case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
             case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
             case CONTENT_CHANGE_TYPE_PANE_TITLE: return "CONTENT_CHANGE_TYPE_PANE_TITLE";
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 4db6f4f..d9fa9f2 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -267,12 +268,14 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
-                if (windows != null) {
+                SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                        sAccessibilityCache.getWindowsOnAllDisplays();
+                List<AccessibilityWindowInfo> windows;
+                if (allWindows != null) {
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Windows cache hit");
                     }
-                    return windows;
+                    return allWindows.valueAt(Display.DEFAULT_DISPLAY);
                 }
                 if (DEBUG) {
                     Log.i(LOG_TAG, "Windows cache miss");
@@ -284,7 +287,9 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (windows != null) {
-                    sAccessibilityCache.setWindows(windows);
+                    allWindows = new SparseArray<>();
+                    allWindows.put(Display.DEFAULT_DISPLAY, windows);
+                    sAccessibilityCache.setWindowsOnAllDisplays(allWindows);
                     return windows;
                 }
             } else {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index d474b4d..cf29ed7 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -744,6 +744,7 @@
     private CharSequence mHintText;
     private CharSequence mError;
     private CharSequence mPaneTitle;
+    private CharSequence mStateDescription;
     private CharSequence mContentDescription;
     private CharSequence mTooltipText;
     private String mViewIdResourceName;
@@ -2723,6 +2724,15 @@
     }
 
     /**
+     * Get the state description of this node.
+     *
+     * @return the state description
+     */
+    public @Nullable CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
      * Gets the content description of this node.
      *
      * @return The content description.
@@ -2731,6 +2741,25 @@
         return mContentDescription;
     }
 
+
+    /**
+     * Sets the state description of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param stateDescription the state description of this node.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        enforceNotSealed();
+        mStateDescription = (stateDescription == null) ? null
+                : stateDescription.subSequence(0, stateDescription.length());
+    }
+
     /**
      * Sets the content description of this node.
      * <p>
@@ -3373,6 +3402,10 @@
         fieldIndex++;
         if (!Objects.equals(mError, DEFAULT.mError)) nonDefaultFields |= bitAt(fieldIndex);
         fieldIndex++;
+        if (!Objects.equals(mStateDescription, DEFAULT.mStateDescription)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mContentDescription, DEFAULT.mContentDescription)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -3505,6 +3538,7 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mText);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mHintText);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mError);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mStateDescription);
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             parcel.writeCharSequence(mContentDescription);
         }
@@ -3582,6 +3616,7 @@
         mOriginalText = other.mOriginalText;
         mHintText = other.mHintText;
         mError = other.mError;
+        mStateDescription = other.mStateDescription;
         mContentDescription = other.mContentDescription;
         mPaneTitle = other.mPaneTitle;
         mTooltipText = other.mTooltipText;
@@ -3706,6 +3741,7 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) mText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mHintText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mError = parcel.readCharSequence();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mStateDescription = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mContentDescription = parcel.readCharSequence();
         }
@@ -4004,6 +4040,7 @@
         builder.append("; text: ").append(mText);
         builder.append("; error: ").append(mError);
         builder.append("; maxTextLength: ").append(mMaxTextLength);
+        builder.append("; stateDescription: ").append(mStateDescription);
         builder.append("; contentDescription: ").append(mContentDescription);
         builder.append("; tooltipText: ").append(mTooltipText);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
@@ -4745,8 +4782,8 @@
         /**
          * Obtains a pooled instance.
          *
-         * @param rowCount The number of rows.
-         * @param columnCount The number of columns.
+         * @param rowCount The number of rows, or -1 if count is unknown.
+         * @param columnCount The number of columns, or -1 if count is unknown.
          * @param hierarchical Whether the collection is hierarchical.
          */
         public static CollectionInfo obtain(int rowCount, int columnCount,
@@ -4800,7 +4837,7 @@
         /**
          * Gets the number of rows.
          *
-         * @return The row count.
+         * @return The row count, or -1 if count is unknown.
          */
         public int getRowCount() {
             return mRowCount;
@@ -4809,7 +4846,7 @@
         /**
          * Gets the number of columns.
          *
-         * @return The column count.
+         * @return The column count, or -1 if count is unknown.
          */
         public int getColumnCount() {
             return mColumnCount;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 4b25378..f4c7b96 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -44,28 +44,126 @@
  * View itself. Similarly the returned instance is responsible for performing accessibility
  * actions on any virtual view or the root view itself. For example:
  * </p>
- * <pre>
- *     getAccessibilityNodeProvider(
- *         if (mAccessibilityNodeProvider == null) {
- *             mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
- *                 public boolean performAction(int action, int virtualDescendantId) {
- *                     // Implementation.
+ * <div>
+ * <div class="ds-selector-tabs"><section><h3 id="kotlin">Kotlin</h3>
+ * <pre class="prettyprint lang-kotlin">
+ * // "view" is the View instance on which this class performs accessibility functions.
+ * class MyCalendarViewAccessibilityDelegate(
+ *       private var view: MyCalendarView) : AccessibilityDelegate() {
+ *     override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider {
+ *         return object : AccessibilityNodeProvider() {
+ *             override fun createAccessibilityNodeInfo(virtualViewId: Int):
+ *                     AccessibilityNodeInfo? {
+ *                 when (virtualViewId) {
+ *                     <var>host-view-id</var> -&gt; {
+ *                         val node = AccessibilityNodeInfo.obtain(view)
+ *                         node.addChild(view, <var>child-view-id</var>)
+ *                         // Set other attributes like screenReaderFocusable
+ *                         // and contentDescription.
+ *                         return node
+ *                     }
+ *                     <var>child-view-id</var> -&gt; {
+ *                         val node = AccessibilityNodeInfo
+ *                                 .obtain(view, virtualViewId)
+ *                         node.setParent(view)
+ *                         node.addAction(ACTION_SCROLL_UP)
+ *                         node.addAction(ACTION_SCROLL_DOWN)
+ *                         // Set other attributes like focusable and visibleToUser.
+ *                         node.setBoundsInScreen(
+ *                                 Rect(<var>coords-of-edges-relative-to-screen</var>))
+ *                         return node
+ *                     }
+ *                     else -&gt; return null
+ *                 }
+ *             }
+ *
+ *             override fun performAction(
+ *                 virtualViewId: Int,
+ *                 action: Int,
+ *                 arguments: Bundle
+ *             ): Boolean {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     return view.performAccessibilityAction(action, arguments)
+ *                 }
+ *                 when (action) {
+ *                     ACTION_SCROLL_UP.id -&gt; {
+ *                         // Implement logic in a separate method.
+ *                         navigateToPreviousMonth()
+ *
+ *                         return true
+ *                     }
+ *                     ACTION_SCROLL_DOWN.id -&gt;
+ *                         // Implement logic in a separate method.
+ *                         navigateToNextMonth()
+ *
+ *                         return true
+ *                     else -&gt; return false
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ * </section><section><h3 id="java">Java</h3>
+ * <pre class="prettyprint lang-java">
+ * final class MyCalendarViewAccessibilityDelegate extends AccessibilityDelegate {
+ *     // The View instance on which this class performs accessibility functions.
+ *     private final MyCalendarView view;
+ *
+ *     MyCalendarViewAccessibilityDelegate(MyCalendarView view) {
+ *         this.view = view;
+ *     }
+ *
+ *     &#64;Override
+ *     public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ *         return new AccessibilityNodeProvider() {
+ *             &#64;Override
+ *             &#64;Nullable
+ *             public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(view);
+ *                     node.addChild(view, <var>child-view-id</var>);
+ *                     // Set other attributes like screenReaderFocusable and contentDescription.
+ *                     return node;
+ *                 } else if (virtualViewId == <var>child-view-id</var>) {
+ *                     AccessibilityNodeInfo node =
+ *                         AccessibilityNodeInfo.obtain(view, virtualViewId);
+ *                     node.setParent(view);
+ *                     node.addAction(ACTION_SCROLL_UP);
+ *                     node.addAction(ACTION_SCROLL_DOWN);
+ *                     // Set other attributes like focusable and visibleToUser.
+ *                     node.setBoundsInScreen(
+ *                         new Rect(<var>coordinates-of-edges-relative-to-screen</var>));
+ *                     return node;
+ *                 } else {
+ *                     return null;
+ *                 }
+ *             }
+ *
+ *             &#64;Override
+ *             public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ *                 if (virtualViewId == <var>host-view-id</var>) {
+ *                     return view.performAccessibilityAction(action, arguments);
+ *                 }
+ *
+ *                 if (action == ACTION_SCROLL_UP.getId()) {
+ *                     // Implement logic in a separate method.
+ *                     navigateToPreviousMonth();
+ *
+ *                     return true;
+ *                 } else if (action == ACTION_SCROLL_DOWN.getId()) {
+ *                     // Implement logic in a separate method.
+ *                     navigateToNextMonth();
+ *
+ *                     return true;
+ *                 } else {
  *                     return false;
  *                 }
- *
- *                 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text,
- *                         int virtualDescendantId) {
- *                     // Implementation.
- *                     return null;
- *                 }
- *
- *                 public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
- *                     // Implementation.
- *                     return null;
- *                 }
- *             });
- *     return mAccessibilityNodeProvider;
- * </pre>
+ *             }
+ *         };
+ *     }
+ * }
+ * </pre></section></div></div>
  */
 public abstract class AccessibilityNodeProvider {
 
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 3b60aee..b03732a 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -636,9 +636,12 @@
      *
      * @param bg The background color.  If 0, no background.  Currently must
      * be black, with any desired alpha level.
+     *
+     * @deprecated None of window animations are running with background color.
      */
+    @Deprecated
     public void setBackgroundColor(@ColorInt int bg) {
-        mBackgroundColor = bg;
+        // The background color is not needed any more, do nothing.
     }
 
     /**
@@ -665,6 +668,7 @@
      *
      * @deprecated All window animations are running with detached wallpaper.
      */
+    @Deprecated
     public void setDetachWallpaper(boolean detachWallpaper) {
     }
 
@@ -793,10 +797,13 @@
 
     /**
      * Returns the background color behind the animation.
+     *
+     * @deprecated None of window animations are running with background color.
      */
+    @Deprecated
     @ColorInt
     public int getBackgroundColor() {
-        return mBackgroundColor;
+        return 0;
     }
 
     /**
@@ -805,6 +812,7 @@
      *
      * @deprecated All window animations are running with detached wallpaper.
      */
+    @Deprecated
     public boolean getDetachWallpaper() {
         return true;
     }
diff --git a/core/java/android/view/contentcapture/ChildContentCaptureSession.java b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
index b3b0b72..5e02de4 100644
--- a/core/java/android/view/contentcapture/ChildContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ChildContentCaptureSession.java
@@ -89,6 +89,11 @@
     }
 
     @Override
+    public void internalNotifySessionLifecycle(boolean started) {
+        getMainCaptureSession().notifySessionLifecycle(mId, started);
+    }
+
+    @Override
     boolean isContentCaptureEnabled() {
         return getMainCaptureSession().isContentCaptureEnabled();
     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index bd38629..c29d251 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -373,6 +373,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 625fcda..cf08c18 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -439,6 +439,19 @@
     public abstract void internalNotifyViewTreeEvent(boolean started);
 
     /**
+     * Notifies the Content Capture Service that a session has paused/resumed.
+     *
+     * @param started whether session has resumed.
+     */
+    public final void notifySessionLifecycle(boolean started) {
+        if (!isContentCaptureEnabled()) return;
+
+        internalNotifySessionLifecycle(started);
+    }
+
+    abstract void internalNotifySessionLifecycle(boolean started);
+
+    /**
      * Creates a {@link ViewStructure} for a "standard" view.
      *
      * <p>This method should be called after a visible view is laid out; the view then must populate
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 1e7440b..349ef09 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -583,6 +583,11 @@
     }
 
     @Override
+    public void internalNotifySessionLifecycle(boolean started) {
+        notifySessionLifecycle(mId, started);
+    }
+
+    @Override
     boolean isContentCaptureEnabled() {
         return super.isContentCaptureEnabled() && mManager.isContentCaptureEnabled();
     }
@@ -637,10 +642,9 @@
         sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
     }
 
-    /** Public because is also used by ViewRootImpl */
-    public void notifySessionLifecycle(boolean started) {
+    void notifySessionLifecycle(int sessionId, boolean started) {
         final int type = started ? TYPE_SESSION_RESUMED : TYPE_SESSION_PAUSED;
-        sendEvent(new ContentCaptureEvent(mId, type), FORCE_FLUSH);
+        sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
     }
 
     void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 0dd2587..e035c62 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -211,16 +211,19 @@
         return mParentAutofillId;
     }
 
+    @Nullable
     @Override
     public AutofillId getAutofillId() {
         return mAutofillId;
     }
 
+    @Nullable
     @Override
     public CharSequence getText() {
         return mText != null ? mText.mText : null;
     }
 
+    @Nullable
     @Override
     public String getClassName() {
         return mClassName;
@@ -231,16 +234,19 @@
         return mId;
     }
 
+    @Nullable
     @Override
     public String getIdPackage() {
         return mIdPackage;
     }
 
+    @Nullable
     @Override
     public String getIdType() {
         return mIdType;
     }
 
+    @Nullable
     @Override
     public String getIdEntry() {
         return mIdEntry;
@@ -341,21 +347,25 @@
         return (mFlags & FLAGS_OPAQUE) != 0;
     }
 
+    @Nullable
     @Override
     public CharSequence getContentDescription() {
         return mContentDescription;
     }
 
+    @Nullable
     @Override
     public Bundle getExtras() {
         return mExtras;
     }
 
+    @Nullable
     @Override
     public String getHint() {
         return mText != null ? mText.mHint : null;
     }
 
+    @Nullable
     @Override
     public String getHintIdEntry() {
         return mHintIdEntry;
@@ -391,11 +401,13 @@
         return mText != null ? mText.mTextStyle : 0;
     }
 
+    @Nullable
     @Override
     public int[] getTextLineCharOffsets() {
         return mText != null ? mText.mLineCharOffsets : null;
     }
 
+    @Nullable
     @Override
     public int[] getTextLineBaselines() {
         return mText != null ? mText.mLineBaselines : null;
@@ -426,6 +438,7 @@
         return mMaxLength;
     }
 
+    @Nullable
     @Override
     public String getTextIdEntry() {
         return mTextIdEntry;
@@ -451,6 +464,7 @@
         return mAutofillOptions;
     }
 
+    @Nullable
     @Override
     public LocaleList getLocaleList() {
         return mLocaleList;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d302c2b..032af1c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -707,9 +707,10 @@
                         if (mBindSequence != bindSequence) {
                             return;
                         }
-                        if (matrixValues == null) {
-                            // That this app is unbound from the parent ActivityView. In this case,
-                            // calling updateCursorAnchorInfo() isn't safe.  Only clear the matrix.
+                        if (matrixValues == null || mActivityViewToScreenMatrix == null) {
+                            // Either InputBoundResult#mActivityViewToScreenMatrixValues is null
+                            // OR this app is unbound from the parent ActivityView. In this case,
+                            // calling updateCursorAnchorInfo() isn't safe. Only clear the matrix.
                             mActivityViewToScreenMatrix = null;
                             return;
                         }
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 0473f54..4554fdc 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -1,3 +1,3 @@
 alanv@google.com
-ashleyrose@google.com
-aurimas@google.com
\ No newline at end of file
+aurimas@google.com
+emberr@google.com
diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS
new file mode 100644
index 0000000..893a083
--- /dev/null
+++ b/core/java/android/view/textclassifier/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 709498
+
+toki@google.com
+tonymak@google.com
+zilka@google.com
+jalt@google.com
+joannechung@google.com
+svetoslavganov@google.com
+eugeniom@google.com
+samsellem@google.com
\ No newline at end of file
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 87be30f..2af7ac7 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2812,7 +2812,7 @@
     }
 
     @Override
-    public void onProvideContentCaptureStructure(ViewStructure structure, int flags) {
+    public void onProvideContentCaptureStructure(@NonNull ViewStructure structure, int flags) {
         mProvider.getViewDelegate().onProvideContentCaptureStructure(structure, flags);
     }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cac75cfd..57ce28e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2561,7 +2561,8 @@
      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
      */
     private boolean shouldBlink() {
-        if (!isCursorVisible() || !mTextView.isFocused()) return false;
+        if (!isCursorVisible() || !mTextView.isFocused() ||
+                !mTextView.isVisibleToUser()) return false;
 
         final int start = mTextView.getSelectionStart();
         if (start < 0) return false;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 2f44d6e..1cb1148 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -4109,7 +4109,7 @@
         final int rowsCount = getCount();
         final int selectionMode = getSelectionModeForAccessibility();
         final CollectionInfo collectionInfo = CollectionInfo.obtain(
-                rowsCount, 1, false, selectionMode);
+                -1, -1, false, selectionMode);
         info.setCollectionInfo(collectionInfo);
 
         if (rowsCount > 0) {
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index d3c6972..f58b6d1 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -800,6 +800,7 @@
         // The surface we allocate for the magnifier content + shadow.
         private final SurfaceSession mSurfaceSession;
         private final SurfaceControl mSurfaceControl;
+        private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
         private final Surface mSurface;
         // The renderer used for the allocated surface.
         private final ThreadedRenderer.SimpleRenderer mRenderer;
@@ -1081,16 +1082,16 @@
                             return;
                         }
                         // Show or move the window at the content draw frame.
-                        SurfaceControl.openTransaction();
-                        mSurfaceControl.deferTransactionUntil(mSurface, frame);
+                        mTransaction.deferTransactionUntilSurface(mSurfaceControl, mSurface, frame);
                         if (updateWindowPosition) {
-                            mSurfaceControl.setPosition(pendingX, pendingY);
+                            mTransaction.setPosition(mSurfaceControl, pendingX, pendingY);
                         }
                         if (firstDraw) {
-                            mSurfaceControl.setLayer(SURFACE_Z);
-                            mSurfaceControl.show();
+                            mTransaction.setLayer(mSurfaceControl, SURFACE_Z)
+                                .show(mSurfaceControl);
+
                         }
-                        SurfaceControl.closeTransaction();
+                        mTransaction.apply();
                     };
                     mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
                 } else {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index eb93fdf..1cc8ff6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -71,9 +71,9 @@
  * instead of scroll view which offers greater user interface flexibility and
  * support for the material design scrolling patterns.</p>
  *
- * <p>To learn more about material design patterns for handling scrolling, see
- * <a href="https://material.io/guidelines/patterns/scrolling-techniques.html#">
- * Scrolling techniques</a>.</p>
+ * <p>Material Design offers guidelines on how the appearance of
+ * <a href="https://material.io/components/">several UI components</a>, including app bars and
+ * banners, should respond to gestures.</p>
  *
  * @attr ref android.R.styleable#ScrollView_fillViewport
  */
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index faeecda..cae1f38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -24,6 +24,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -120,6 +121,7 @@
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -178,6 +180,20 @@
     private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
     private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
 
+    public static final int TARGET_TYPE_DEFAULT = 0;
+    public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
+    public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
+    public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+
+    @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
+            TARGET_TYPE_DEFAULT,
+            TARGET_TYPE_CHOOSER_TARGET,
+            TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
+            TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShareTargetType {}
+
     /**
      * The transition time between placeholders for direct share to a message
      * indicating that non are available.
@@ -218,9 +234,9 @@
     private int mCurrAvailableWidth = 0;
 
     /** {@link ChooserActivity#getBaseScore} */
-    private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
+    public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
     /** {@link ChooserActivity#getBaseScore} */
-    private static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
+    public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
     // TODO: Update to handle landscape instead of using static value
     private static final int MAX_RANKED_TARGETS = 4;
@@ -443,7 +459,7 @@
                     }
                     if (sri.resultTargets != null) {
                         mChooserListAdapter.addServiceResults(sri.originalTarget,
-                                sri.resultTargets, false);
+                                sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
                     }
                     unbindService(sri.connection);
                     sri.connection.destroy();
@@ -474,7 +490,7 @@
                     final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
                     if (resultInfo.resultTargets != null) {
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
-                                resultInfo.resultTargets, true);
+                                resultInfo.resultTargets, msg.arg1);
                     }
                     break;
 
@@ -493,6 +509,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         final long intentReceivedTime = System.currentTimeMillis();
+        // This is the only place this value is being set. Effectively final.
+        mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
+
         mIsSuccessfullySelected = false;
         Intent intent = getIntent();
         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
@@ -619,9 +638,6 @@
                 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
                 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
 
-        // This is the only place this value is being set. Effectively final.
-        mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
-
         AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
         if (appPredictor != null) {
             mDirectShareAppTargetCache = new HashMap<>();
@@ -714,7 +730,13 @@
     /**
      * Returns true if app prediction service is defined and the component exists on device.
      */
-    private boolean isAppPredictionServiceAvailable() {
+    @VisibleForTesting
+    public boolean isAppPredictionServiceAvailable() {
+        if (getPackageManager().getAppPredictionServicePackageName() == null) {
+            // Default AppPredictionService is not defined.
+            return false;
+        }
+
         final String appPredictionServiceName =
                 getString(R.string.config_defaultAppPredictionService);
         if (appPredictionServiceName == null) {
@@ -797,6 +819,11 @@
             clipboardManager.setPrimaryClip(clipData);
             Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
 
+            // Log share completion via copy
+            LogMaker targetLogMaker = new LogMaker(
+                    MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
+            getMetricsLogger().write(targetLogMaker);
+
             finish();
         }
     }
@@ -1209,7 +1236,7 @@
         mChooserListAdapter = (ChooserListAdapter) adapter;
         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
             mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
-                    false);
+                    TARGET_TYPE_DEFAULT);
         }
         mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
         if (listView != null) {
@@ -1555,33 +1582,32 @@
             }
         }
 
+        // If |appTargets| is not null, results are from AppPredictionService and already sorted.
+        final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
+                TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+
         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
         // for direct share targets. After ShareSheet is refactored we should use the
         // ShareShortcutInfos directly.
         boolean resultMessageSent = false;
         for (int i = 0; i < driList.size(); i++) {
-            List<ChooserTarget> chooserTargets = new ArrayList<>();
+            List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
             for (int j = 0; j < resultList.size(); j++) {
                 if (driList.get(i).getResolvedComponentName().equals(
                             resultList.get(j).getTargetComponent())) {
-                    ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
-                    // Incoming results are ordered but without a score. Create a score
-                    // based on the index in order to be sorted appropriately when joined
-                    // with legacy direct share api results.
-                    float score = Math.max(1.0f - (0.05f * j), 0.0f);
-                    ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
-                    chooserTargets.add(chooserTarget);
-                    if (mDirectShareAppTargetCache != null && appTargets != null) {
-                        mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
-                    }
+                    matchingShortcuts.add(resultList.get(j));
                 }
             }
-            if (chooserTargets.isEmpty()) {
+            if (matchingShortcuts.isEmpty()) {
                 continue;
             }
+            List<ChooserTarget> chooserTargets = convertToChooserTarget(
+                    matchingShortcuts, resultList, appTargets, shortcutType);
+
             final Message msg = Message.obtain();
             msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
             msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+            msg.arg1 = shortcutType;
             mChooserHandler.sendMessage(msg);
             resultMessageSent = true;
         }
@@ -1615,23 +1641,69 @@
         return false;
     }
 
-    private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
-                                                 float score) {
-        ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
-        Bundle extras = new Bundle();
-        extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
-        return new ChooserTarget(
-                // The name of this target.
-                shortcutInfo.getShortLabel(),
-                // Don't load the icon until it is selected to be shown
-                null,
-                // The ranking score for this target (0.0-1.0); the system will omit items with low
-                // scores when there are too many Direct Share items.
-                score,
-                // The name of the component to be launched if this target is chosen.
-                shareShortcut.getTargetComponent().clone(),
-                // The extra values here will be merged into the Intent when this target is chosen.
-                extras);
+    /**
+     * Converts a list of ShareShortcutInfos to ChooserTargets.
+     * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
+     *                         share intent filter.
+     * @param allShortcuts List of all the shortcuts from all the packages on the device that are
+     *                    returned for the current sharing action.
+     * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
+     * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
+     *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
+     * @return A list of ChooserTargets sorted by score in descending order.
+     */
+    @VisibleForTesting
+    @NonNull
+    public List<ChooserTarget> convertToChooserTarget(
+            @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
+            @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
+            @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
+        // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
+        // list instead of the actual rank value when converting a rank to a score.
+        List<Integer> scoreList = new ArrayList<>();
+        if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+            for (int i = 0; i < matchingShortcuts.size(); i++) {
+                int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
+                if (!scoreList.contains(shortcutRank)) {
+                    scoreList.add(shortcutRank);
+                }
+            }
+            Collections.sort(scoreList);
+        }
+
+        List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
+        for (int i = 0; i < matchingShortcuts.size(); i++) {
+            ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
+            int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
+
+            float score;
+            if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
+                // Incoming results are ordered. Create a score based on index in the original list.
+                score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
+            } else {
+                // Create a score based on the rank of the shortcut.
+                int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
+                score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
+            }
+
+            Bundle extras = new Bundle();
+            extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
+            ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
+                    null, // Icon will be loaded later if this target is selected to be shown.
+                    score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
+
+            chooserTargetList.add(chooserTarget);
+            if (mDirectShareAppTargetCache != null && allAppTargets != null) {
+                mDirectShareAppTargetCache.put(chooserTarget,
+                        allAppTargets.get(indexInAllShortcuts));
+            }
+        }
+
+        // Sort ChooserTargets by score in descending order
+        Comparator<ChooserTarget> byScore =
+                (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
+        Collections.sort(chooserTargetList, byScore);
+        return chooserTargetList;
     }
 
     private String convertServiceName(String packageName, String serviceName) {
@@ -1666,10 +1738,6 @@
         mServiceConnections.clear();
     }
 
-    public void onSetupVoiceInteraction() {
-        // Do nothing. We'll send the voice stuff ourselves.
-    }
-
     private void logDirectShareTargetReceived(int logCategory) {
         final long queryTime =
                 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
@@ -1727,8 +1795,7 @@
         if (!mIsAppPredictorComponentAvailable) {
             return null;
         }
-        if (mAppPredictor == null
-                    && getPackageManager().getAppPredictionServicePackageName() != null) {
+        if (mAppPredictor == null) {
             final IntentFilter filter = getTargetIntentFilter();
             Bundle extras = new Bundle();
             extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
@@ -2676,7 +2743,7 @@
          * if score is too low.
          */
         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
-                boolean isShortcutResult) {
+                @ShareTargetType int targetType) {
             if (DEBUG) {
                 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
                         + " targets");
@@ -2686,9 +2753,12 @@
                 return;
             }
 
-            final float baseScore = getBaseScore(origTarget, isShortcutResult);
+            final float baseScore = getBaseScore(origTarget, targetType);
             Collections.sort(targets, mBaseTargetComparator);
 
+            final boolean isShortcutResult =
+                    (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+                            || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
             final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
                                        : MAX_CHOOSER_TARGETS_PER_APP;
             float lastScore = 0;
@@ -2739,17 +2809,17 @@
           *   <li>Legacy direct share targets
           * </ol>
           */
-        private float getBaseScore(DisplayResolveInfo target, boolean isShortcutResult) {
+        public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
             if (target == null) {
                 return CALLER_TARGET_SCORE_BOOST;
             }
 
-            if (isShortcutResult && getAppPredictorForDirectShareIfEnabled() != null) {
+            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
                 return SHORTCUT_TARGET_SCORE_BOOST;
             }
 
             float score = super.getScore(target);
-            if (isShortcutResult) {
+            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
                 return score * SHORTCUT_TARGET_SCORE_BOOST;
             }
 
diff --git a/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
new file mode 100644
index 0000000..163e4f7
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.app;
+
+import android.app.AsyncNotedAppOp;
+
+// Interface to observe async noted appops
+oneway interface IAppOpsAsyncNotedCallback {
+    void opNoted(in AsyncNotedAppOp op);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 72dbbf3..8f6c950 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,12 +17,13 @@
 package com.android.internal.app;
 
 import android.app.AppOpsManager;
-import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.os.RemoteCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 
 interface IAppOpsService {
@@ -40,6 +41,10 @@
     IBinder getToken(IBinder clientToken);
     int permissionToOpCode(String permission);
     int checkAudioOperation(int code, int usage, int uid, String packageName);
+    void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+            String message);
+    boolean shouldCollectNotes(int opCode);
+    void setCameraAudioRestriction(int mode);
     // End of methods also called by native code.
     // Any new method exposed to native must be added after the last one, do not reorder
 
@@ -82,6 +87,10 @@
     void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
     void stopWatchingNoted(IAppOpsNotedCallback callback);
 
+    void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+    void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+    List<AsyncNotedAppOp> extractAsyncOps(String packageName);
+
     int checkOperationRaw(int code, int uid, String packageName);
 
     void reloadNonHistoricalState();
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 764c0cf..ea24d5f 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -54,4 +54,6 @@
     boolean isRecognitionActive(in ParcelUuid parcelUuid);
 
     int getModelState(in ParcelUuid soundModelId);
+
+    @nullable SoundTrigger.ModuleProperties getModuleProperties();
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 58ce03b..407a85f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -361,9 +361,6 @@
 
         initSuspendedColorMatrix();
 
-        if (isVoiceInteraction()) {
-            onSetupVoiceInteraction();
-        }
         final Set<String> categories = intent.getCategories();
         MetricsLogger.action(this, mAdapter.hasFilteredItem()
                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -442,24 +439,21 @@
         mSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix);
     }
 
-    /**
-     * Perform any initialization needed for voice interaction.
-     */
-    public void onSetupVoiceInteraction() {
-        // Do it right now. Subclasses may delay this and send it later.
-        sendVoiceChoicesIfNeeded();
-    }
-
     public void sendVoiceChoicesIfNeeded() {
         if (!isVoiceInteraction()) {
             // Clearly not needed.
             return;
         }
 
-
         final Option[] options = new Option[mAdapter.getCount()];
         for (int i = 0, N = options.length; i < N; i++) {
-            options[i] = optionForChooserTarget(mAdapter.getItem(i), i);
+            TargetInfo target = mAdapter.getItem(i);
+            if (target == null) {
+                // If this occurs, a new set of targets is being loaded. Let that complete,
+                // and have the next call to send voice choices proceed instead.
+                return;
+            }
+            options[i] = optionForChooserTarget(target, i);
         }
 
         mPickOptionRequest = new PickTargetOptionRequest(
@@ -1872,7 +1866,7 @@
                 }
             }
 
-
+            sendVoiceChoicesIfNeeded();
             postListReadyRunnable();
         }
 
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index a7f7fe1..7cbacad 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -27,5 +27,6 @@
     void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
     void providersChanged();
     void viewDataChanged(int appWidgetId, int viewId);
+    void appWidgetRemoved(int appWidgetId);
 }
 
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
new file mode 100644
index 0000000..1ce071b
--- /dev/null
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.internal.compat;
+
+import android.util.StatsLog;
+
+/**
+ * A helper class to report changes to stats log.
+ *
+ * @hide
+ */
+public final class ChangeReporter {
+
+    /**
+     * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+     *
+     * @param state to transform
+     * @return a string representing the state
+     */
+    private static String stateToString(int state) {
+        switch (state) {
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+                return "LOGGED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+                return "ENABLED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+                return "DISABLED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Constructs and returns a string to be logged to logcat when a change is reported.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return string to log
+     */
+    public static String createLogString(int uid, long changeId, int state) {
+        return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
+                stateToString(state));
+    }
+
+    /**
+     * Report the change to stats log.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @param source   of the logging - app process or system server
+     */
+    public void reportChange(int uid, long changeId, int state, int source) {
+        //TODO(b/138374585): Implement rate limiting for stats log.
+        StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+                state, source);
+    }
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 7c50337..5142d3c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -50,10 +50,19 @@
     // Flags related to controls
 
     /**
-     * (boolean) Wether to have split behavior when opening QS
+     * (boolean) Whether to have split behavior when opening QS
      */
     public static final String QS_SPLIT_ENABLED = "qs_split_enabled";
 
+    /**
+     * (int) Open settings panels for WiFi and BT tiles
+     * 0 - default behavior, link to settings
+     * 1 - open panel on long press, click remains the same
+     * 2 - open panel on click, long press remains the same
+     * 3 - use details on long press
+     */
+    public static final String QS_USE_SETTINGS_PANELS = "qs_use_settings_panels";
+
     // Flags related to Smart Suggestions - these are read in SmartReplyConstants.
 
     /** (boolean) Whether to enable smart suggestions in notifications. */
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
deleted file mode 100644
index a72a2f5..0000000
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
-import android.os.FileUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Helper class for performing atomic operations on a file, by creating a
- * backup file until a write has successfully completed.
- * <p>
- * Atomic file guarantees file integrity by ensuring that a file has
- * been completely written and sync'd to disk before removing its backup.
- * As long as the backup file exists, the original file is considered
- * to be invalid (left over from a previous attempt to write the file).
- * </p><p>
- * Atomic file does not confer any file locking semantics.
- * Do not use this class when the file may be accessed or modified concurrently
- * by multiple threads or processes.  The caller is responsible for ensuring
- * appropriate mutual exclusion invariants whenever it accesses the file.
- * </p>
- */
-public final class AtomicFile {
-    private final File mBaseName;
-    private final File mBackupName;
-    
-    @UnsupportedAppUsage
-    public AtomicFile(File baseName) {
-        mBaseName = baseName;
-        mBackupName = new File(baseName.getPath() + ".bak");
-    }
-    
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public File getBaseFile() {
-        return mBaseName;
-    }
-    
-    @UnsupportedAppUsage
-    public FileOutputStream startWrite() throws IOException {
-        // Rename the current file so it may be used as a backup during the next read
-        if (mBaseName.exists()) {
-            if (!mBackupName.exists()) {
-                if (!mBaseName.renameTo(mBackupName)) {
-                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
-                            + " to backup file " + mBackupName);
-                }
-            } else {
-                mBaseName.delete();
-            }
-        }
-        FileOutputStream str = null;
-        try {
-            str = new FileOutputStream(mBaseName);
-        } catch (FileNotFoundException e) {
-            File parent = mBaseName.getParentFile();
-            if (!parent.mkdir()) {
-                throw new IOException("Couldn't create directory " + mBaseName);
-            }
-            FileUtils.setPermissions(
-                parent.getPath(),
-                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                -1, -1);
-            try {
-                str = new FileOutputStream(mBaseName);
-            } catch (FileNotFoundException e2) {
-                throw new IOException("Couldn't create " + mBaseName);
-            }
-        }
-        return str;
-    }
-    
-    @UnsupportedAppUsage
-    public void finishWrite(FileOutputStream str) {
-        if (str != null) {
-            FileUtils.sync(str);
-            try {
-                str.close();
-                mBackupName.delete();
-            } catch (IOException e) {
-                Log.w("AtomicFile", "finishWrite: Got exception:", e);
-            }
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public void failWrite(FileOutputStream str) {
-        if (str != null) {
-            FileUtils.sync(str);
-            try {
-                str.close();
-                mBaseName.delete();
-                mBackupName.renameTo(mBaseName);
-            } catch (IOException e) {
-                Log.w("AtomicFile", "failWrite: Got exception:", e);
-            }
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public FileOutputStream openAppend() throws IOException {
-        try {
-            return new FileOutputStream(mBaseName, true);
-        } catch (FileNotFoundException e) {
-            throw new IOException("Couldn't append " + mBaseName);
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public void truncate() throws IOException {
-        try {
-            FileOutputStream fos = new FileOutputStream(mBaseName);
-            FileUtils.sync(fos);
-            fos.close();
-        } catch (FileNotFoundException e) {
-            throw new IOException("Couldn't append " + mBaseName);
-        } catch (IOException e) {
-        }
-    }
-
-    public boolean exists() {
-        return mBaseName.exists() || mBackupName.exists();
-    }
-
-    public void delete() {
-        mBaseName.delete();
-        mBackupName.delete();
-    }
-
-    @UnsupportedAppUsage
-    public FileInputStream openRead() throws FileNotFoundException {
-        if (mBackupName.exists()) {
-            mBaseName.delete();
-            mBackupName.renameTo(mBaseName);
-        }
-        return new FileInputStream(mBaseName);
-    }
-    
-    @UnsupportedAppUsage
-    public byte[] readFully() throws IOException {
-        FileInputStream stream = openRead();
-        try {
-            int pos = 0;
-            int avail = stream.available();
-            byte[] data = new byte[avail];
-            while (true) {
-                int amt = stream.read(data, pos, data.length-pos);
-                //Log.i("foo", "Read " + amt + " bytes at " + pos
-                //        + " of avail " + data.length);
-                if (amt <= 0) {
-                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                    //        + " len=" + data.length);
-                    return data;
-                }
-                pos += amt;
-                avail = stream.available();
-                if (avail > data.length-pos) {
-                    byte[] newData = new byte[pos+avail];
-                    System.arraycopy(data, 0, newData, 0, pos);
-                    data = newData;
-                }
-            }
-        } finally {
-            stream.close();
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 392f074..bdb65f3 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8503ab8..9bddd2a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -60,11 +60,13 @@
 import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
+import android.telephony.ModemActivityInfo.TransmitPower;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.IntArray;
 import android.util.KeyValueListParser;
 import android.util.Log;
@@ -10975,7 +10977,7 @@
     }
 
     private ModemActivityInfo mLastModemActivityInfo =
-            new ModemActivityInfo(0, 0, 0, new int[0], 0, 0);
+            new ModemActivityInfo(0, 0, 0, new int[0], 0);
 
     private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) {
         if (activityInfo == null) {
@@ -10983,15 +10985,14 @@
         }
         int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
         for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
-            txTimeMs[i] = activityInfo.getTxTimeMillis()[i]
-                    - mLastModemActivityInfo.getTxTimeMillis()[i];
+            txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis()
+                    - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis();
         }
         ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(),
                 activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(),
                 activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(),
                 txTimeMs,
-                activityInfo.getRxTimeMillis() - mLastModemActivityInfo.getRxTimeMillis(),
-                activityInfo.getEnergyUsed() - mLastModemActivityInfo.getEnergyUsed());
+                activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis());
         mLastModemActivityInfo = activityInfo;
         return deltaInfo;
     }
@@ -11034,10 +11035,11 @@
                         deltaInfo.getIdleTimeMillis());
                 mModemActivity.getSleepTimeCounter().addCountLocked(
                         deltaInfo.getSleepTimeMillis());
-                mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getRxTimeMillis());
+                mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis());
                 for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                     mModemActivity.getTxTimeCounters()[lvl]
-                        .addCountLocked(deltaInfo.getTxTimeMillis()[lvl]);
+                        .addCountLocked(deltaInfo.getTransmitPowerInfo()
+                            .get(lvl).getTimeInMillis());
                 }
 
                 // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
@@ -11049,13 +11051,13 @@
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP)
                             + deltaInfo.getIdleTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
-                            + deltaInfo.getRxTimeMillis() *
+                            + deltaInfo.getReceiveTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-                    int[] txTimeMs = deltaInfo.getTxTimeMillis();
-                    for (int i = 0; i < Math.min(txTimeMs.length,
+                    List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo();
+                    for (int i = 0; i < Math.min(txPowerInfo.size(),
                             SignalStrength.NUM_SIGNAL_STRENGTH_BINS); i++) {
-                        energyUsed += txTimeMs[i] * mPowerProfile.getAveragePower(
-                                PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+                        energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile
+                            .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
                     }
 
                     // We store the power drain as mAms.
@@ -11144,15 +11146,16 @@
                             ControllerActivityCounterImpl activityCounter =
                                     u.getOrCreateModemControllerActivityLocked();
                             if (totalRxPackets > 0 && entry.rxPackets > 0) {
-                                final long rxMs = (entry.rxPackets * deltaInfo.getRxTimeMillis())
-                                        / totalRxPackets;
+                                final long rxMs = (entry.rxPackets
+                                    * deltaInfo.getReceiveTimeMillis()) / totalRxPackets;
                                 activityCounter.getRxTimeCounter().addCountLocked(rxMs);
                             }
 
                             if (totalTxPackets > 0 && entry.txPackets > 0) {
                                 for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                                     long txMs =
-                                            entry.txPackets * deltaInfo.getTxTimeMillis()[lvl];
+                                            entry.txPackets * deltaInfo.getTransmitPowerInfo()
+                                                .get(lvl).getTimeInMillis();
                                     txMs /= totalTxPackets;
                                     activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
                                 }
@@ -11183,15 +11186,16 @@
         if (activityInfo == null) {
             return;
         }
-        int[] txTimeMs = activityInfo.getTxTimeMillis();
-        if (txTimeMs == null || txTimeMs.length != ModemActivityInfo.TX_POWER_LEVELS) {
+        List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo();
+        if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) {
             return;
         }
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         int levelMaxTimeSpent = 0;
-        for (int i = 1; i < txTimeMs.length; i++) {
-            if (txTimeMs[i] > txTimeMs[levelMaxTimeSpent]) {
+        for (int i = 1; i < txPowerInfo.size(); i++) {
+            if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent)
+                .getTimeInMillis()) {
                 levelMaxTimeSpent = i;
             }
         }
@@ -13321,7 +13325,8 @@
             return;
         }
 
-        if (mBatteryStatsHistory.getActiveFile() == null) {
+        final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
+        if (activeHistoryFile == null) {
             Slog.w(TAG,
                     "readLocked: no history file associated with this instance");
             return;
@@ -13332,14 +13337,16 @@
         Parcel stats = Parcel.obtain();
         try {
             final long start = SystemClock.uptimeMillis();
-            byte[] raw = mStatsFile.readFully();
-            stats.unmarshall(raw, 0, raw.length);
-            stats.setDataPosition(0);
-            readSummaryFromParcel(stats);
-            if (DEBUG) {
-                Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
-                        + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
-                        - start));
+            if (mStatsFile.exists()) {
+                byte[] raw = mStatsFile.readFully();
+                stats.unmarshall(raw, 0, raw.length);
+                stats.setDataPosition(0);
+                readSummaryFromParcel(stats);
+                if (DEBUG) {
+                    Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error reading battery statistics", e);
@@ -13351,17 +13358,19 @@
         Parcel history = Parcel.obtain();
         try {
             final long start = SystemClock.uptimeMillis();
-            byte[] raw = mBatteryStatsHistory.getActiveFile().readFully();
-            if (raw.length > 0) {
-                history.unmarshall(raw, 0, raw.length);
-                history.setDataPosition(0);
-                readHistoryBuffer(history, true);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "readLocked history file::"
-                        + mBatteryStatsHistory.getActiveFile().getBaseFile().getPath()
-                        + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
-                        - start));
+            if (activeHistoryFile.exists()) {
+                byte[] raw = activeHistoryFile.readFully();
+                if (raw.length > 0) {
+                    history.unmarshall(raw, 0, raw.length);
+                    history.setDataPosition(0);
+                    readHistoryBuffer(history, true);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "readLocked history file::"
+                            + activeHistoryFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error reading battery history", e);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 5b129f4..1de2e72 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -20,7 +20,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
-import android.content.type.MimeMapImpl;
 import android.os.Build;
 import android.os.DeadObjectException;
 import android.os.Debug;
@@ -34,9 +33,6 @@
 import com.android.server.NetworkManagementSocketTagger;
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
-
-import libcore.net.MimeMap;
-
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -214,14 +210,6 @@
         RuntimeHooks.setTimeZoneIdSupplier(() -> SystemProperties.get("persist.sys.timezone"));
 
         /*
-         * Replace libcore's minimal default mapping between MIME types and file
-         * extensions with a mapping that's suitable for Android. Android's mapping
-         * contains many more entries that are derived from IANA registrations but
-         * with several customizations (extensions, overrides).
-         */
-        MimeMap.setDefault(MimeMapImpl.createDefaultInstance());
-
-        /*
          * Sets handler for java.util.logging to use Android log facilities.
          * The odd "new instance-and-then-throw-away" is a mirror of how
          * the "java.util.logging.config.class" system property works. We
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9ea9b06..3be1a1a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -92,11 +92,6 @@
     private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020;
     private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030;
 
-    /**
-     * when preloading, GC after allocating this many bytes
-     */
-    private static final int PRELOAD_GC_THRESHOLD = 50000;
-
     private static final String ABI_LIST_ARG = "--abi-list=";
 
     // TODO (chriswailes): Re-name this --zygote-socket-name= and then add a
@@ -281,11 +276,6 @@
             droppedPriviliges = true;
         }
 
-        // Alter the target heap utilization.  With explicit GCs this
-        // is not likely to have any effect.
-        float defaultUtilization = runtime.getTargetHeapUtilization();
-        runtime.setTargetHeapUtilization(0.8f);
-
         try {
             BufferedReader br =
                     new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
@@ -301,9 +291,6 @@
 
                 Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                 try {
-                    if (false) {
-                        Log.v(TAG, "Preloading " + line + "...");
-                    }
                     // Load and explicitly initialize the given class. Use
                     // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
                     // (to derive the caller's class-loader). Use true to force initialization, and
@@ -334,8 +321,6 @@
             Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
         } finally {
             IoUtils.closeQuietly(is);
-            // Restore default.
-            runtime.setTargetHeapUtilization(defaultUtilization);
 
             // Fill in dex caches with classes, fields, and methods brought in by preloading.
             Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");
@@ -475,7 +460,7 @@
         ZygoteHooks.gcAndFinalize();
     }
 
-    private static boolean profileSystemServer() {
+    private static boolean shouldProfileSystemServer() {
         boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver",
                 /*default=*/ false);
         // Can't use DeviceConfig since it's not initialized at this point.
@@ -507,7 +492,7 @@
             }
             // Capturing profiles is only supported for debug or eng builds since selinux normally
             // prevents it.
-            if (profileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+            if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
                 try {
                     Log.d(TAG, "Preparing system server profile");
                     prepareSystemServerProfile(systemServerClasspath);
@@ -764,7 +749,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",
+                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
                 "--capabilities=" + capabilities + "," + capabilities,
                 "--nice-name=system_server",
                 "--runtime-args",
@@ -780,8 +765,7 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
-            if (profileSystemServer()) {
-
+            if (shouldProfileSystemServer()) {
                 parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 981d0bb..b02563a 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -804,7 +804,9 @@
         updateElevation();
         mAllowUpdateElevation = true;
 
-        if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
+        if (changed
+                && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
+                    || mDrawLegacyNavigationBarBackground)) {
             getViewRootImpl().requestInvalidateRootRenderNode();
         }
     }
diff --git a/core/java/com/android/internal/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
new file mode 100644
index 0000000..964be01
--- /dev/null
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.policy;
+
+
+/**
+ * Stores a snapshot of window information used to decide whether to intercept a key event.
+ */
+public class KeyInterceptionInfo {
+    // Window layout params attributes.
+    public final int layoutParamsType;
+    public final int layoutParamsPrivateFlags;
+    // Debug friendly name to help identify the window
+    public final String windowTitle;
+
+    public KeyInterceptionInfo(int type, int flags, String title) {
+        layoutParamsType = type;
+        layoutParamsPrivateFlags = flags;
+        windowTitle = title;
+    }
+}
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
index c8afdd4..2d3b450 100644
--- a/core/java/com/android/internal/util/AnnotationValidations.java
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -61,16 +61,52 @@
     }
 
     public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
-            String paramName1, int param1, String paramName2, int param2) {
+            String paramName1, long param1, String paramName2, long param2) {
         validate(annotation, ignored, value, paramName1, param1);
         validate(annotation, ignored, value, paramName2, param2);
     }
 
     public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
-            String paramName, int param) {
+            String paramName, long param) {
         switch (paramName) {
-            case "from": if (value < param) invalid(annotation, value, paramName, param); break;
-            case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+            case "from":
+                if (value < param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+            case "to":
+                if (value > param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Validate a long value with two parameters.
+     */
+    public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+            String paramName1, long param1, String paramName2, long param2) {
+        validate(annotation, ignored, value, paramName1, param1);
+        validate(annotation, ignored, value, paramName2, param2);
+    }
+
+    /**
+     * Validate a long value with one parameter.
+     */
+    public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+            String paramName, long param) {
+        switch (paramName) {
+            case "from":
+                if (value < param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+            case "to":
+                if (value > param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
         }
     }
 
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 7fd94c6..cac691c 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,6 +1,7 @@
 package com.android.internal.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -13,6 +14,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import java.util.function.Consumer;
+
 public class ScreenshotHelper {
     private static final String TAG = "ScreenshotHelper";
 
@@ -34,17 +37,58 @@
     }
 
     /**
-     * Request a screenshot be taken.
+     * Request a screenshot be taken with a specific timeout.
      *
-     * @param screenshotType The type of screenshot, for example either
-     *                       {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
-     *                       or {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
-     * @param hasStatus {@code true} if the status bar is currently showing. {@code false} if not.
-     * @param hasNav {@code true} if the navigation bar is currently showing. {@code false} if not.
-     * @param handler A handler used in case the screenshot times out
+     * Added to support reducing unit test duration; the method variant without a timeout argument
+     * is recommended for general use.
+     *
+     * @param screenshotType     The type of screenshot, for example either
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
+     *                           or
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
+     *                           if
+     *                           not.
+     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
+     *                           false}
+     *                           if not.
+     * @param handler            A handler used in case the screenshot times out
+     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+     *                           screenshot was taken.
      */
     public void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, @NonNull Handler handler) {
+            final boolean hasNav, @NonNull Handler handler,
+            @Nullable Consumer<Boolean> completionConsumer) {
+        takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
+                completionConsumer);
+    }
+
+    /**
+     * Request a screenshot be taken.
+     *
+     * Added to support reducing unit test duration; the method variant without a timeout argument
+     * is recommended for general use.
+     *
+     * @param screenshotType     The type of screenshot, for example either
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
+     *                           or
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
+     *                           if
+     *                           not.
+     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
+     *                           false}
+     *                           if not.
+     * @param timeoutMs          If the screenshot hasn't been completed within this time period,
+     *                           the screenshot attempt will be cancelled and `completionConsumer`
+     *                           will be run.
+     * @param handler            A handler used in case the screenshot times out
+     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+     *                           screenshot was taken.
+     */
+    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+            final boolean hasNav, long timeoutMs, @NonNull Handler handler,
+            @Nullable Consumer<Boolean> completionConsumer) {
         synchronized (mScreenshotLock) {
             if (mScreenshotConnection != null) {
                 return;
@@ -54,7 +98,8 @@
             final Intent serviceIntent = new Intent();
 
             final Runnable mScreenshotTimeout = new Runnable() {
-                @Override public void run() {
+                @Override
+                public void run() {
                     synchronized (mScreenshotLock) {
                         if (mScreenshotConnection != null) {
                             mContext.unbindService(mScreenshotConnection);
@@ -62,6 +107,9 @@
                             notifyScreenshotError();
                         }
                     }
+                    if (completionConsumer != null) {
+                        completionConsumer.accept(false);
+                    }
                 }
             };
 
@@ -86,15 +134,22 @@
                                         handler.removeCallbacks(mScreenshotTimeout);
                                     }
                                 }
+                                if (completionConsumer != null) {
+                                    completionConsumer.accept(true);
+                                }
                             }
                         };
                         msg.replyTo = new Messenger(h);
-                        msg.arg1 = hasStatus ? 1: 0;
-                        msg.arg2 = hasNav ? 1: 0;
+                        msg.arg1 = hasStatus ? 1 : 0;
+                        msg.arg2 = hasNav ? 1 : 0;
+
                         try {
                             messenger.send(msg);
                         } catch (RemoteException e) {
                             Log.e(TAG, "Couldn't take screenshot: " + e);
+                            if (completionConsumer != null) {
+                                completionConsumer.accept(false);
+                            }
                         }
                     }
                 }
@@ -115,7 +170,7 @@
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                     UserHandle.CURRENT)) {
                 mScreenshotConnection = conn;
-                handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS);
+                handler.postDelayed(mScreenshotTimeout, timeoutMs);
             }
         }
     }
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index 3cb6919..b54e5fb 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -229,6 +229,7 @@
      *                    default (undefined) change type or one or more of:
      *         <ul>
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b36c3fa..b626fc6 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -42,7 +42,7 @@
     long getLong(in String key, in long defaultValue, in int userId);
     @UnsupportedAppUsage
     String getString(in String key, in String defaultValue, in int userId);
-    void setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
+    boolean setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
     void resetKeyStore(int userId);
     VerifyCredentialResponse checkCredential(in byte[] credential, int type, int userId,
             in ICheckCredentialProgressCallback progressCallback);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 32867a8..070121c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -680,6 +680,15 @@
 
     /**
      * Clear any lock pattern or password.
+
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
+     * @param savedCredential The previously saved credential
+     * @param userHandle the user whose pattern is to be saved.
+     * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean clearLock(byte[] savedCredential, int userHandle) {
         return clearLock(savedCredential, userHandle, false);
@@ -687,19 +696,27 @@
 
     /**
      * Clear any lock pattern or password, with the option to ignore incorrect existing credential.
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
+     * @param savedCredential The previously saved credential
+     * @param userHandle the user whose pattern is to be saved.
+     * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean clearLock(byte[] savedCredential, int userHandle, boolean allowUntrustedChange) {
         final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
         setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userHandle);
 
-        try{
-            getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE,
-                    savedCredential, PASSWORD_QUALITY_UNSPECIFIED, userHandle,
-                    allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to clear lock", e);
+        try {
+            if (!getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
+                    PASSWORD_QUALITY_UNSPECIFIED, userHandle, allowUntrustedChange)) {
+                return false;
+            }
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            return false;
+            throw new RuntimeException("Failed to clear lock", e);
         }
 
         if (userHandle == UserHandle.USER_SYSTEM) {
@@ -747,11 +764,15 @@
 
     /**
      * Save a lock pattern.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+     * is incorrect, or if the lockscreen verification is still being throttled.
+     *
      * @param pattern The new pattern to save.
      * @param savedPattern The previously saved pattern, converted to byte[] format
      * @param userId the user whose pattern is to be saved.
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
             int userId) {
@@ -760,13 +781,17 @@
 
     /**
      * Save a lock pattern.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+     * is incorrect, or if the lockscreen verification is still being throttled.
+     *
      * @param pattern The new pattern to save.
      * @param savedPattern The previously saved pattern, converted to byte[] format
      * @param userId the user whose pattern is to be saved.
      * @param allowUntrustedChange whether we want to allow saving a new password if the existing
      * password being provided is incorrect.
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
             int userId, boolean allowUntrustedChange) {
@@ -783,12 +808,13 @@
         final int currentQuality = getKeyguardStoredPasswordQuality(userId);
         setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId);
         try {
-            getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN, savedPattern,
-                    PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Couldn't save lock pattern", e);
+            if (!getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN,
+                    savedPattern, PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange)) {
+                return false;
+            }
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userId);
-            return false;
+            throw new RuntimeException("Couldn't save lock pattern", e);
         }
         // Update the device encryption password.
         if (userId == UserHandle.USER_SYSTEM
@@ -906,14 +932,18 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
      * android.content.ComponentName)}
      * @param userHandle The userId of the user to change the password for
-     *
      * @return whether this was successful or not.
-     *
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      * @deprecated Pass password as a byte array
      */
     @Deprecated
@@ -928,13 +958,18 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
      * android.content.ComponentName)}
      * @param userHandle The userId of the user to change the password for
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
             int userHandle) {
@@ -946,6 +981,11 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
@@ -953,9 +993,9 @@
      * @param userHandle The userId of the user to change the password for
      * @param allowUntrustedChange whether we want to allow saving a new password if the existing
      * password being provided is incorrect.
-     *
      * @return whether this method saved the new password successfully or not. This flow will fail
      * and return false if the given credential is wrong and allowUntrustedChange is false.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPassword(byte[] password, byte[] savedPassword,
             int requestedQuality, int userHandle, boolean allowUntrustedChange) {
@@ -981,10 +1021,9 @@
         try {
             getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
                     requestedQuality, userHandle, allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to save lock password", e);
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            return false;
+            throw new RuntimeException("Unable to save lock password", e);
         }
 
         updateEncryptionPasswordIfNeeded(password, passwordQuality, userHandle);
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index d459bfb..aab2f4b 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
+import android.app.admin.PasswordMetrics;
 
 /**
  * LockSettingsService local system service interface.
@@ -61,4 +63,18 @@
             long tokenHandle, byte[] token, int requestedQuality, int userId);
 
     public abstract boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId);
+
+    /**
+     * Returns PasswordMetrics object corresponding to the given user's lockscreen password.
+     * If the user has a password but its metrics isn't known yet (for example if the device
+     * has not been unlocked since boot), this method will return {@code null}.
+     * If the user has no password, a default PasswordMetrics (PASSWORD_QUALITY_UNSPECIFIED)
+     * will be returned.
+     *
+     * Calling this method on a managed profile user with unified challenge is undefined.
+     *
+     * @param userHandle the user for whom to provide metrics.
+     * @return the user password metrics.
+     */
+    public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle);
 }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 364278d..7cd3e95 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -53,6 +53,8 @@
 
 /**
  * Loads global system configuration info.
+ * Note: Initializing this class hits the disk and is slow.  This class should generally only be
+ * accessed by the system_server process.
  */
 public class SystemConfig {
     static final String TAG = "SystemConfig";
@@ -208,6 +210,11 @@
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
 
     public static SystemConfig getInstance() {
+        if (!isSystemProcess()) {
+            Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
+                    + "system_server.");
+        }
+
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
                 sInstance = new SystemConfig();
@@ -1161,4 +1168,8 @@
             mSplitPermissions.add(new SplitPermissionInfo(splitPerm, newPermissions, targetSdk));
         }
     }
+
+    private static boolean isSystemProcess() {
+        return Process.myUid() == Process.SYSTEM_UID;
+    }
 }
diff --git a/core/java/com/package.html b/core/java/com/package.html
new file mode 100644
index 0000000..8f35da9
--- /dev/null
+++ b/core/java/com/package.html
@@ -0,0 +1,8 @@
+<!--
+  This file is to hide classes in com.* packages from SDK
+-->
+<html>
+<body>
+    {@hide}
+</body>
+</html>
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 4c54539..5a0f16e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -42,10 +42,6 @@
     ],
 
     include_dirs: [
-        // we need to access the private Bionic header
-        // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
-        "bionic/libc/private",
-
         "external/skia/include/private",
         "frameworks/base/media/jni",
         "system/media/camera/include",
@@ -277,6 +273,7 @@
                 "libnativewindow",
             ],
             generated_sources: ["android_util_StatsLogInternal.cpp"],
+            header_libs: ["bionic_libc_platform_headers"],
         },
         host: {
             cflags: [
@@ -308,6 +305,7 @@
                 "android_os_MessageQueue.cpp",
                 "android_os_Trace.cpp",
                 "android_util_AssetManager.cpp",
+                "android_util_FileObserver.cpp",
                 "android_util_StringBlock.cpp",
                 "android_util_XmlBlock.cpp",
             ],
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c12940a..49b9eba 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1163,9 +1163,9 @@
         setenv("ANDROID_ROOT", rootDir, 1);
     }
 
-    const char* runtimeRootDir = getenv("ANDROID_RUNTIME_ROOT");
-    if (runtimeRootDir == NULL) {
-        LOG_FATAL("No runtime directory specified with ANDROID_RUNTIME_ROOT environment variable.");
+    const char* artRootDir = getenv("ANDROID_ART_ROOT");
+    if (artRootDir == NULL) {
+        LOG_FATAL("No ART directory specified with ANDROID_ART_ROOT environment variable.");
         return;
     }
 
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 549fd4d..6c0680f 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -70,6 +70,7 @@
 extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
 extern int register_android_graphics_text_LineBreaker(JNIEnv* env);
 extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
+extern int register_android_os_FileObserver(JNIEnv* env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
@@ -128,6 +129,7 @@
     {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
     {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)},
 #ifdef __linux__
+    {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
     {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
 #endif
     {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 2987c5e..11d321f 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -220,10 +220,10 @@
 {
     ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class));
 
-    ir->set(env->GetIntField(obj, gRect_leftFieldID),
-            env->GetIntField(obj, gRect_topFieldID),
-            env->GetIntField(obj, gRect_rightFieldID),
-            env->GetIntField(obj, gRect_bottomFieldID));
+    ir->setLTRB(env->GetIntField(obj, gRect_leftFieldID),
+                env->GetIntField(obj, gRect_topFieldID),
+                env->GetIntField(obj, gRect_rightFieldID),
+                env->GetIntField(obj, gRect_bottomFieldID));
     return ir;
 }
 
@@ -241,10 +241,10 @@
 {
     ALOG_ASSERT(env->IsInstanceOf(obj, gRectF_class));
 
-    r->set(env->GetFloatField(obj, gRectF_leftFieldID),
-           env->GetFloatField(obj, gRectF_topFieldID),
-           env->GetFloatField(obj, gRectF_rightFieldID),
-           env->GetFloatField(obj, gRectF_bottomFieldID));
+    r->setLTRB(env->GetFloatField(obj, gRectF_leftFieldID),
+               env->GetFloatField(obj, gRectF_topFieldID),
+               env->GetFloatField(obj, gRectF_rightFieldID),
+               env->GetFloatField(obj, gRectF_bottomFieldID));
     return r;
 }
 
@@ -252,10 +252,10 @@
 {
     ALOG_ASSERT(env->IsInstanceOf(obj, gRect_class));
 
-    r->set(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)),
-           SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)),
-           SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)),
-           SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID)));
+    r->setLTRB(SkIntToScalar(env->GetIntField(obj, gRect_leftFieldID)),
+               SkIntToScalar(env->GetIntField(obj, gRect_topFieldID)),
+               SkIntToScalar(env->GetIntField(obj, gRect_rightFieldID)),
+               SkIntToScalar(env->GetIntField(obj, gRect_bottomFieldID)));
     return r;
 }
 
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index 1fb5fe3..87662f7 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -61,7 +61,7 @@
 
 static jboolean Region_setRect(JNIEnv* env, jobject, jlong dstHandle, jint left, jint top, jint right, jint bottom) {
     SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle);
-    bool result = dst->setRect(left, top, right, bottom);
+    bool result = dst->setRect({left, top, right, bottom});
     return boolTojboolean(result);
 }
 
@@ -92,10 +92,7 @@
 
 static jboolean Region_op0(JNIEnv* env, jobject, jlong dstHandle, jint left, jint top, jint right, jint bottom, jint op) {
     SkRegion* dst = reinterpret_cast<SkRegion*>(dstHandle);
-    SkIRect ir;
-
-    ir.set(left, top, right, bottom);
-    bool result = dst->op(ir, (SkRegion::Op)op);
+    bool result = dst->op({left, top, right, bottom}, (SkRegion::Op)op);
     return boolTojboolean(result);
 }
 
@@ -139,13 +136,13 @@
 }
 
 static jboolean Region_quickContains(JNIEnv* env, jobject region, jint left, jint top, jint right, jint bottom) {
-    bool result = GetSkRegion(env, region)->quickContains(left, top, right, bottom);
+    bool result = GetSkRegion(env, region)->quickContains({left, top, right, bottom});
     return boolTojboolean(result);
 }
 
 static jboolean Region_quickRejectIIII(JNIEnv* env, jobject region, jint left, jint top, jint right, jint bottom) {
     SkIRect ir;
-    ir.set(left, top, right, bottom);
+    ir.setLTRB(left, top, right, bottom);
     bool result = GetSkRegion(env, region)->quickReject(ir);
     return boolTojboolean(result);
 }
@@ -224,7 +221,7 @@
 
     SkRegion* region = new SkRegion;
     for (size_t x = 0; x + 4 <= rects.size(); x += 4) {
-        region->op(rects[x], rects[x+1], rects[x+2], rects[x+3], SkRegion::kUnion_Op);
+        region->op({rects[x], rects[x+1], rects[x+2], rects[x+3]}, SkRegion::kUnion_Op);
     }
 
     return reinterpret_cast<jlong>(region);
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index 5f83038..ca8b8de 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -23,7 +23,7 @@
 #include "core_jni_helpers.h"
 #include <unistd.h>
 
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 namespace android {
 
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 076e99d..2ca4500 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -23,7 +23,7 @@
 #include "core_jni_helpers.h"
 
 #include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 #include <utils/Log.h>
 #include <utils/String8.h>
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index b499981..bc69735 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -1023,6 +1023,41 @@
     }
 }
 
+static void android_hardware_Camera_setAudioRestriction(
+        JNIEnv *env, jobject thiz, jint mode)
+{
+    ALOGV("setAudioRestriction");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) {
+        jniThrowRuntimeException(env, "camera has been disconnected");
+        return;
+    }
+
+    int32_t ret = camera->setAudioRestriction(mode);
+    if (ret < 0) {
+        jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+        return;
+    }
+}
+
+static int32_t android_hardware_Camera_getAudioRestriction(
+        JNIEnv *env, jobject thiz)
+{
+    ALOGV("getAudioRestriction");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) {
+        jniThrowRuntimeException(env, "camera has been disconnected");
+        return -1;
+    }
+
+    int32_t ret = camera->getGlobalAudioRestriction();
+    if (ret < 0) {
+        jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+        return -1;
+    }
+    return ret;
+}
+
 //-------------------------------------------------
 
 static const JNINativeMethod camMethods[] = {
@@ -1107,6 +1142,12 @@
   { "enableFocusMoveCallback",
     "(I)V",
     (void *)android_hardware_Camera_enableFocusMoveCallback},
+  { "setAudioRestriction",
+    "(I)V",
+    (void *)android_hardware_Camera_setAudioRestriction},
+  { "getAudioRestriction",
+    "()I",
+    (void *)android_hardware_Camera_getAudioRestriction},
 };
 
 struct field {
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 03057dc..0002f8b 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -606,12 +606,12 @@
         goto exit;
     }
     memory = memoryDealer->allocate(offset + size);
-    if (memory == 0 || memory->pointer() == NULL) {
+    if (memory == 0 || memory->unsecurePointer() == NULL) {
         status = SOUNDTRIGGER_STATUS_ERROR;
         goto exit;
     }
 
-    nSoundModel = (struct sound_trigger_sound_model *)memory->pointer();
+    nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
 
     nSoundModel->type = type;
     nSoundModel->uuid = nUuid;
@@ -737,18 +737,18 @@
         return SOUNDTRIGGER_STATUS_ERROR;
     }
     sp<IMemory> memory = memoryDealer->allocate(totalSize);
-    if (memory == 0 || memory->pointer() == NULL) {
+    if (memory == 0 || memory->unsecurePointer() == NULL) {
         return SOUNDTRIGGER_STATUS_ERROR;
     }
     if (dataSize != 0) {
-        memcpy((char *)memory->pointer() + sizeof(struct sound_trigger_recognition_config),
+        memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
                 nData,
                 dataSize);
         env->ReleaseByteArrayElements(jData, nData, 0);
     }
     env->DeleteLocalRef(jData);
     struct sound_trigger_recognition_config *config =
-                                    (struct sound_trigger_recognition_config *)memory->pointer();
+                                    (struct sound_trigger_recognition_config *)memory->unsecurePointer();
     config->data_size = dataSize;
     config->data_offset = sizeof(struct sound_trigger_recognition_config);
     config->capture_requested = env->GetBooleanField(jConfig,
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index daa6347..c5049ec 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -649,7 +649,7 @@
         if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
             sizeInBytes = track->sharedBuffer()->size();
         }
-        memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
+        memcpy(track->sharedBuffer()->unsecurePointer(), data + offsetInSamples, sizeInBytes);
         written = sizeInBytes;
     }
     if (written >= 0) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 13e1dfa..9c52a64 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -34,7 +34,7 @@
 #include <vector>
 
 #include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 #include <debuggerd/client.h>
 #include <log/log.h>
 #include <utils/misc.h>
@@ -564,11 +564,13 @@
 
     // Read system memory info including ZRAM. The values are stored in the vector
     // in the same order as MEMINFO_* enum
-    std::vector<uint64_t> mem(MEMINFO_COUNT);
-    std::vector<std::string> tags(::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags);
+    std::vector<std::string_view> tags(
+        ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.begin(),
+        ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.end());
     tags.insert(tags.begin() + MEMINFO_ZRAM_TOTAL, "Zram:");
+    std::vector<uint64_t> mem(tags.size());
     ::android::meminfo::SysMemInfo smi;
-    if (!smi.ReadMemInfo(tags, &mem)) {
+    if (!smi.ReadMemInfo(tags.size(), tags.data(), mem.data())) {
         jniThrowRuntimeException(env, "SysMemInfo read failed");
         return;
     }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cbae2da..b6427c9a 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -152,7 +152,7 @@
         uint32_t flags,
         TransactCallback callback) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    bool isOneway = (flags & TF_ONE_WAY) != 0;
+    bool isOneway = (flags & IBinder::FLAG_ONEWAY) != 0;
     ScopedLocalRef<jobject> replyObj(env, nullptr);
     sp<JHwParcel> replyContext = nullptr;
 
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index ab52314..cfdf26a 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -108,7 +108,7 @@
     if (!ConvertKeyAndForward(env, keyJ, true, handler)) {
         // Must have been a failure in SetProperty.
         jniThrowException(env, "java/lang/RuntimeException",
-                          "failed to set system property");
+                          "failed to set system property (check logcat for reason)");
     }
 }
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 2d7069c..8aa6f86 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,7 +28,12 @@
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <android-base/unique_fd.h>
 
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -58,8 +63,18 @@
 
 static const bool kDebugPolicy = false;
 static const bool kDebugProc = false;
-// When reading `proc` files, how many bytes to read at a time
-static const int kReadSize = 4096;
+
+// Stack reservation for reading small proc files.  Most callers of
+// readProcFile() are reading files under this threshold, e.g.,
+// /proc/pid/stat.  /proc/pid/time_in_state ends up being about 520
+// bytes, so use 1024 for the stack to provide a bit of slack.
+static const ssize_t kProcReadStackBufferSize = 1024;
+
+// The other files we read from proc tend to be a bit larger (e.g.,
+// /proc/stat is about 3kB), so once we exhaust the stack buffer,
+// retry with a relatively large heap-allocated buffer.  We double
+// this size and retry until the whole file fits.
+static const ssize_t kProcReadMinHeapBufferSize = 4096;
 
 #if GUARD_THREAD_PRIORITY
 Mutex gKeyCreateMutex;
@@ -616,14 +631,16 @@
 
 static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz)
 {
-    static const std::vector<std::string> memFreeTags = {
+    std::array<std::string_view, 2> memFreeTags = {
         ::android::meminfo::SysMemInfo::kMemFree,
         ::android::meminfo::SysMemInfo::kMemCached,
     };
     std::vector<uint64_t> mem(memFreeTags.size());
     ::android::meminfo::SysMemInfo smi;
 
-    if (!smi.ReadMemInfo(memFreeTags, &mem)) {
+    if (!smi.ReadMemInfo(memFreeTags.size(),
+                         memFreeTags.data(),
+                         mem.data())) {
         jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory");
         return -1L;
     }
@@ -644,6 +661,12 @@
     return si.totalram;
 }
 
+/*
+ * The outFields array is initialized to -1 to allow the caller to identify
+ * when the status file (and therefore the process) they specified is invalid.
+ * This array should not be overwritten or cleared before we know that the
+ * status file can be read.
+ */
 void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr,
                                       jobjectArray reqFields, jlongArray outFields)
 {
@@ -692,14 +715,14 @@
         return;
     }
 
-    //ALOGI("Clearing %" PRId32 " sizes", count);
-    for (i=0; i<count; i++) {
-        sizesArray[i] = 0;
-    }
-
     int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
 
     if (fd >= 0) {
+        //ALOGI("Clearing %" PRId32 " sizes", count);
+        for (i=0; i<count; i++) {
+            sizesArray[i] = 0;
+        }
+
         const size_t BUFFER_SIZE = 4096;
         char* buffer = (char*)malloc(BUFFER_SIZE);
         int len = read(fd, buffer, BUFFER_SIZE-1);
@@ -1004,9 +1027,9 @@
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return JNI_FALSE;
     }
-    int fd = open(file8, O_RDONLY | O_CLOEXEC);
 
-    if (fd < 0) {
+    ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+    if (!fd.ok()) {
         if (kDebugProc) {
             ALOGW("Unable to open process file: %s\n", file8);
         }
@@ -1015,35 +1038,52 @@
     }
     env->ReleaseStringUTFChars(file, file8);
 
-    std::vector<char> fileBuffer(kReadSize);
-    int numBytesRead = 0;
-    while (true) {
-        // Resize buffer to make space for contents. This might be more than we need, but once we've
-        // read we resize back down
-        fileBuffer.resize(numBytesRead + kReadSize, 0);
-        // Read in contents
-        int len = TEMP_FAILURE_RETRY(read(fd, fileBuffer.data() + numBytesRead, kReadSize));
-        numBytesRead += len;
-        if (len < 0) {
-            // If `len` is negative, an error occurred on read
+    // Most proc files we read are small, so we only go through the
+    // loop once and use the stack buffer.  We allocate a buffer big
+    // enough for the whole file.
+
+    char readBufferStack[kProcReadStackBufferSize];
+    std::unique_ptr<char[]> readBufferHeap;
+    char* readBuffer = &readBufferStack[0];
+    ssize_t readBufferSize = kProcReadStackBufferSize;
+    ssize_t numberBytesRead;
+    for (;;) {
+        // By using pread, we can avoid an lseek to rewind the FD
+        // before retry, saving a system call.
+        numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
+        if (numberBytesRead < 0 && errno == EINTR) {
+            continue;
+        }
+        if (numberBytesRead < 0) {
             if (kDebugProc) {
-                ALOGW("Unable to open process file: %s fd=%d\n", file8, fd);
+                ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
             }
-            close(fd);
             return JNI_FALSE;
-        } else if (len == 0) {
-            // If nothing read, we're done
+        }
+        if (numberBytesRead < readBufferSize) {
             break;
         }
+        if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+            if (kDebugProc) {
+                ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+            }
+            return JNI_FALSE;
+        }
+        readBufferSize = std::max(readBufferSize * 2,
+                                  kProcReadMinHeapBufferSize);
+        readBufferHeap.reset();  // Free address space before getting more.
+        readBufferHeap = std::make_unique<char[]>(readBufferSize);
+        if (!readBufferHeap) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+            return JNI_FALSE;
+        }
+        readBuffer = readBufferHeap.get();
     }
-    // Resize back down to the amount we read
-    fileBuffer.resize(numBytesRead);
-    // Terminate buffer with null byte
-    fileBuffer.push_back('\0');
-    close(fd);
 
-    return android_os_Process_parseProcLineArray(env, clazz, fileBuffer.data(), 0, numBytesRead,
-            format, outStrings, outLongs, outFloats);
+    // parseProcLineArray below modifies the buffer while parsing!
+    return android_os_Process_parseProcLineArray(
+        env, clazz, readBuffer, 0, numberBytesRead,
+        format, outStrings, outLongs, outFloats);
 }
 
 void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 8553a2c..af34e7b7 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "InputChannel-JNI"
 
+#include "android-base/stringprintf.h"
 #include <nativehelper/JNIHelp.h>
 #include "nativehelper/scoped_utf_chars.h"
 #include <android_runtime/AndroidRuntime.h>
@@ -60,7 +61,7 @@
 // ----------------------------------------------------------------------------
 
 NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
-    mInputChannel(inputChannel), mDisposeCallback(NULL) {
+    mInputChannel(inputChannel), mDisposeCallback(nullptr) {
 }
 
 NativeInputChannel::~NativeInputChannel() {
@@ -74,8 +75,8 @@
 void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
     if (mDisposeCallback) {
         mDisposeCallback(env, obj, mInputChannel, mDisposeData);
-        mDisposeCallback = NULL;
-        mDisposeData = NULL;
+        mDisposeCallback = nullptr;
+        mDisposeData = nullptr;
     }
 }
 
@@ -96,14 +97,14 @@
 sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
-    return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
+    return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr;
 }
 
 void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
         InputChannelObjDisposeCallback callback, void* data) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
-    if (nativeInputChannel == NULL) {
+    if (nativeInputChannel == nullptr) {
         ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
     } else {
         nativeInputChannel->setDisposeCallback(callback, data);
@@ -131,27 +132,27 @@
     status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
 
     if (result) {
-        String8 message;
-        message.appendFormat("Could not open input channel pair.  status=%d", result);
-        jniThrowRuntimeException(env, message.string());
-        return NULL;
+        std::string message = android::base::StringPrintf(
+                "Could not open input channel pair : %s", strerror(-result));
+        jniThrowRuntimeException(env, message.c_str());
+        return nullptr;
     }
 
-    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
+    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
             std::make_unique<NativeInputChannel>(serverChannel));
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
             std::make_unique<NativeInputChannel>(clientChannel));
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
@@ -170,7 +171,7 @@
 
         nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
 
-        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+        android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
         delete nativeInputChannel;
     }
 }
@@ -179,14 +180,14 @@
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     if (nativeInputChannel) {
-        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+        android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
         delete nativeInputChannel;
     }
 }
 
 static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
         jobject otherObj) {
-    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
+    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != nullptr) {
         jniThrowException(env, "java/lang/IllegalStateException",
                 "Other object already has a native input channel.");
         return;
@@ -195,12 +196,12 @@
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
-    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+    android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
 }
 
 static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
         jobject parcelObj) {
-    if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
+    if (android_view_InputChannel_getNativeInputChannel(env, obj) != nullptr) {
         jniThrowException(env, "java/lang/IllegalStateException",
                 "This object already has a native input channel.");
         return;
@@ -222,25 +223,26 @@
 static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
         jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
-    if (parcel) {
-        NativeInputChannel* nativeInputChannel =
-                android_view_InputChannel_getNativeInputChannel(env, obj);
-        if (nativeInputChannel) {
-            sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
-
-            parcel->writeInt32(1);
-            inputChannel->write(*parcel);
-        } else {
-            parcel->writeInt32(0);
-        }
+    if (parcel == nullptr) {
+        ALOGE("Could not obtain parcel for Java object");
+        return;
     }
+
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, obj);
+    if (!nativeInputChannel) {
+        parcel->writeInt32(0); // not initialized
+        return;
+    }
+    parcel->writeInt32(1); // initialized
+    nativeInputChannel->getInputChannel()->write(*parcel);
 }
 
 static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     if (! nativeInputChannel) {
-        return NULL;
+        return nullptr;
     }
 
     jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().c_str());
@@ -250,10 +252,24 @@
 static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
-    if (nativeInputChannel) {
-        android_view_InputChannel_setNativeInputChannel(env, otherObj,
-                new NativeInputChannel(nativeInputChannel->getInputChannel()->dup()));
+    if (nativeInputChannel == nullptr) {
+        jniThrowRuntimeException(env, "InputChannel has no valid NativeInputChannel");
+        return;
     }
+
+    sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
+    if (inputChannel == nullptr) {
+        jniThrowRuntimeException(env, "NativeInputChannel has no corresponding InputChannel");
+        return;
+    }
+    sp<InputChannel> dupInputChannel = inputChannel->dup();
+    if (dupInputChannel == nullptr) {
+        std::string message = android::base::StringPrintf(
+                "Could not duplicate input channel %s", inputChannel->getName().c_str());
+        jniThrowRuntimeException(env, message.c_str());
+    }
+    android_view_InputChannel_setNativeInputChannel(env, otherObj,
+            new NativeInputChannel(dupInputChannel));
 }
 
 static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 7975c86..c380fd5 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -83,7 +83,7 @@
         return mInputConsumer.getChannel()->getName();
     }
 
-    virtual int handleEvent(int receiveFd, int events, void* data);
+    virtual int handleEvent(int receiveFd, int events, void* data) override;
 };
 
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 67f52f4..bf0f10e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -397,15 +397,6 @@
     transaction->setGeometry(ctrl, source, dst, orientation);
 }
 
-static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
-jlong transactionObj,
-        jlong nativeObject) {
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
-    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    transaction->setGeometryAppliesWithResize(ctrl);
-}
-
 static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint w, jint h) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1295,8 +1286,6 @@
             (void*)nativeSetRelativeLayer },
     {"nativeSetPosition", "(JJFF)V",
             (void*)nativeSetPosition },
-    {"nativeSetGeometryAppliesWithResize", "(JJ)V",
-            (void*)nativeSetGeometryAppliesWithResize },
     {"nativeSetSize", "(JJII)V",
             (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d5b875b..d42a48a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -73,7 +73,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 #include <cutils/ashmem.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index 3b891d6..6119d71 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -28,3 +28,13 @@
         "android/bluetooth/smp/enums.proto",
     ],
 }
+
+java_library_host {
+    name: "protolog-proto",
+    srcs: [
+        "android/server/protolog.proto"
+    ],
+    proto: {
+        type: "full",
+    },
+}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 3323095..b8c5270 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2402,6 +2402,10 @@
     // Note: Gear icon is shown next to gesture navigation preference and opens sensitivity dialog
     SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG = 1748;
 
+    // OPEN: Settings > System > Aware > Aware Display
+    // CATEGORY: SETTINGS
+    // OS: Q
+    SETTINGS_AWARE_DISPLAY = 1750;
     // ---- End Q Constants, all Q constants go above this line ----
     // OPEN: Settings > Network & Internet > Wi-Fi > Click new network
     // CATEGORY: SETTINGS
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 57ced09..7fa0ff6 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -32,7 +32,7 @@
     optional float font_scale = 1;
     optional uint32 mcc = 2;
     optional uint32 mnc = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
-    repeated LocaleProto locales = 4;
+    repeated LocaleProto locales = 4 [deprecated = true];
     optional uint32 screen_layout = 5;
     optional uint32 color_mode = 6;
     optional uint32 touchscreen = 7;
@@ -48,6 +48,7 @@
     optional uint32 smallest_screen_width_dp = 17;
     optional uint32 density_dpi = 18;
     optional .android.app.WindowConfigurationProto window_configuration = 19;
+    optional string locale_list = 20;
 }
 
 /**
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
index bae6ec1..a8f2a13 100644
--- a/core/proto/android/content/locale.proto
+++ b/core/proto/android/content/locale.proto
@@ -22,6 +22,7 @@
 package android.content;
 
 message LocaleProto {
+    option deprecated = true;
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
     optional string language = 1;
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f2ca0a4..a568c13 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -688,6 +688,7 @@
         // Configuration options for smart replies and smart actions in notifications. This is
         // encoded as a key=value list separated by commas.
         optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Notification notification = 82;
 
diff --git a/core/proto/android/server/backup_chunks_metadata.proto b/core/proto/android/server/backup_chunks_metadata.proto
deleted file mode 100644
index a375f02..0000000
--- a/core/proto/android/server/backup_chunks_metadata.proto
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-syntax = "proto2";
-package com.android.server.backup.encryption.chunk;
-
-option java_outer_classname = "ChunksMetadataProto";
-
-// Cipher type with which the chunks are encrypted. For now we only support AES/GCM/NoPadding, but
-// this is for backwards-compatibility in case we need to change the default Cipher in the future.
-enum CipherType {
-    UNKNOWN_CIPHER_TYPE = 0;
-    // Chunk is prefixed with a 12-byte nonce. The tag length is 16 bytes.
-    AES_256_GCM = 1;
-}
-
-// Checksum type with which the plaintext is verified.
-enum ChecksumType {
-    UNKNOWN_CHECKSUM_TYPE = 0;
-    SHA_256 = 1;
-}
-
-enum ChunkOrderingType {
-    CHUNK_ORDERING_TYPE_UNSPECIFIED = 0;
-    // The chunk ordering contains a list of the start position of each chunk in the encrypted file,
-    // ordered as in the plaintext file. This allows us to recreate the original plaintext file
-    // during decryption. We use this mode for full backups where the order of the data in the file
-    // is important.
-    EXPLICIT_STARTS = 1;
-    // The chunk ordering does not contain any start positions, and instead each encrypted chunk in
-    // the backup file is prefixed with its length. This allows us to decrypt each chunk but does
-    // not give any information about the order. However, we use this mode for key value backups
-    // where the order does not matter.
-    INLINE_LENGTHS = 2;
-}
-
-// Chunk entry (for local state)
-message Chunk {
-    // SHA-256 MAC of the plaintext of the chunk
-    optional bytes hash = 1;
-    // Number of bytes in encrypted chunk
-    optional int32 length = 2;
-}
-
-// List of the chunks in the blob, along with the length of each chunk. From this is it possible to
-// extract individual chunks. (i.e., start position is equal to the sum of the lengths of all
-// preceding chunks.)
-//
-// This is local state stored on the device. It is never sent to the backup server. See
-// ChunkOrdering for how the device restores the chunks in the correct order.
-// Next tag : 6
-message ChunkListing {
-    repeated Chunk chunks = 1;
-
-    // Cipher algorithm with which the chunks are encrypted.
-    optional CipherType cipher_type = 2;
-
-    // Defines the type of chunk order used to encode the backup file on the server, so that we can
-    // consistently use the same type between backups. If unspecified this backup file was created
-    // before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
-    optional ChunkOrderingType chunk_ordering_type = 5;
-
-    // The document ID returned from Scotty server after uploading the blob associated with this
-    // listing. This needs to be sent when uploading new diff scripts.
-    optional string document_id = 3;
-
-    // Fingerprint mixer salt used for content defined chunking. This is randomly generated for each
-    // package during the initial non-incremental backup and reused for incremental backups.
-    optional bytes fingerprint_mixer_salt = 4;
-}
-
-// Ordering information about plaintext and checksum. This is used on restore to reconstruct the
-// blob in its correct order. (The chunk order is randomized so as to give the server less
-// information about which parts of the backup are changing over time.) This proto is encrypted
-// before being uploaded to the server, with a key unknown to the server.
-message ChunkOrdering {
-    // For backups where ChunksMetadata#chunk_ordering_type = EXPLICIT STARTS:
-    // Ordered start positions of chunks. i.e., the file is the chunk starting at this position,
-    // followed by the chunk starting at this position, followed by ... etc. You can compute the
-    // lengths of the chunks by sorting this list then looking at the start position of the next
-    // chunk after the chunk you care about. This is guaranteed to work as all chunks are
-    // represented in this list.
-    //
-    // For backups where ChunksMetadata#chunk_ordering_type = INLINE_LENGTHS:
-    // This field is unused. See ChunkOrderingType#INLINE_LENGTHS.
-    repeated int32 starts = 1 [packed = true];
-
-    // Checksum of plaintext content. (i.e., in correct order.)
-    //
-    // Each chunk also has a MAC, as generated by GCM, so this is NOT Mac-then-Encrypt, which has
-    // security implications. This is an additional checksum to verify that once the chunks have
-    // been reordered, that the file matches the expected plaintext. This prevents the device
-    // restoring garbage data in case of a mismatch between the ChunkOrdering and the backup blob.
-    optional bytes checksum = 2;
-}
-
-// Additional metadata about a backup blob that needs to be synced to the server. This is used on
-// restore to reconstruct the blob in its correct order. (The chunk order is randomized so as to
-// give the server less information about which parts of the backup are changing over time.) This
-// data structure is only ever uploaded to the server encrypted with a key unknown to the server.
-// Next tag : 6
-message ChunksMetadata {
-    // Cipher algorithm with which the chunk listing and chunks are encrypted.
-    optional CipherType cipher_type = 1;
-
-    // Defines the type of chunk order this metadata contains. If unspecified this backup file was
-    // created before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
-    optional ChunkOrderingType chunk_ordering_type = 5
-    [default = CHUNK_ORDERING_TYPE_UNSPECIFIED];
-
-    // Encrypted bytes of ChunkOrdering
-    optional bytes chunk_ordering = 2;
-
-    // The type of algorithm used for the checksum of the plaintext. (See ChunkOrdering.) This is
-    // for forwards compatibility in case we change the algorithm in the future. For now, always
-    // SHA-256.
-    optional ChecksumType checksum_type = 3;
-
-    // This used to be the plaintext tertiary key. No longer used.
-    reserved 4;
-}
\ No newline at end of file
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 79167ab..15b98af 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -48,6 +48,13 @@
 
     repeated int32 started_users = 2;
 
+    message JobRestriction {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional .android.app.job.StopReasonEnum reason = 1;
+        optional bool is_restricting = 2;
+    }
+
     message RegisteredJob {
         option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -56,20 +63,22 @@
 
         optional bool is_job_ready_to_be_executed = 10;
         // A job is ready to be executed if:
-        // is_job_ready && are_users_started && !is_job_thermal_constrained && !is_job_pending &&
+        // is_job_ready && are_users_started && !is_job_restricted && !is_job_pending &&
         // !is_job_currently_active && !is_uid_backing_up &&
         // is_component_usable.
         optional bool is_job_ready = 3;
         optional bool are_users_started = 4;
-        optional bool is_job_thermal_constrained = 11;
+        optional bool is_job_restricted = 11;
         optional bool is_job_pending = 5;
         optional bool is_job_currently_active = 6;
         optional bool is_uid_backing_up = 7;
         optional bool is_component_usable = 8;
 
+        repeated JobRestriction restrictions = 12;
+
         reserved 9; // last_run_heartbeat
 
-        // Next tag: 12
+        // Next tag: 13
     }
     repeated RegisteredJob registered_jobs = 3;
 
diff --git a/core/proto/android/server/protolog.proto b/core/proto/android/server/protolog.proto
new file mode 100644
index 0000000..7c98d31
--- /dev/null
+++ b/core/proto/android/server/protolog.proto
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.protolog;
+
+option java_multiple_files = true;
+
+/* represents a single log entry */
+message ProtoLogMessage {
+    /* log statement identifier, created from message string and log level. */
+    optional fixed32 message_hash = 1;
+    /* log time, relative to the elapsed system time clock. */
+    optional fixed64 elapsed_realtime_nanos = 2;
+    /* string parameters passed to the log call. */
+    repeated string str_params = 3;
+    /* integer parameters passed to the log call. */
+    repeated sint64 sint64_params = 4 [packed=true];
+    /* floating point parameters passed to the log call. */
+    repeated double double_params = 5 [packed=true];
+    /* boolean parameters passed to the log call. */
+    repeated bool boolean_params = 6 [packed=true];
+}
+
+/* represents a log file containing ProtoLog log entries.
+   Encoded, it should start with 0x9 0x50 0x52 0x4f 0x54 0x4f 0x4c 0x4f 0x47 (.PROTOLOG), such
+   that they can be easily identified. */
+message ProtoLogFileProto {
+    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+        constants into .proto files. */
+    enum MagicNumber {
+        INVALID = 0;
+        MAGIC_NUMBER_L = 0x544f5250; /* PROT (little-endian ASCII) */
+        MAGIC_NUMBER_H = 0x474f4c4f; /* OLOG (little-endian ASCII) */
+    }
+
+    /* the magic number header */
+    optional fixed64 magic_number = 1;
+    /* log proto version. */
+    optional string version = 2;
+    /* offset between real-time clock and elapsed system time clock in miliseconds.
+       Calculated as: (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000) */
+    optional fixed64 realTimeToElapsedTimeOffsetMillis = 3;
+    /* log entries */
+    repeated ProtoLogMessage log = 4;
+}
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index f26eefa..75f265e 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -114,4 +114,6 @@
   repeated UsageStats packages = 20;
   repeated Configuration configurations = 21;
   repeated Event event_log = 22;
+
+  repeated Event pending_events = 23; // TODO: move to usagestatsservice_v2.proto
 }
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 7779025..fd10503 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -196,7 +196,7 @@
     repeated TaskProto tasks = 3;
     optional bool fills_parent = 4;
     optional .android.graphics.RectProto bounds = 5;
-    optional bool animation_background_surface_is_dimming = 6;
+    optional bool animation_background_surface_is_dimming = 6 [deprecated=true];
     optional bool defer_removal = 7;
     optional float minimize_amount = 8;
     optional bool adjusted_for_ime = 9;
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index 655b5e3..f648912 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -184,6 +184,8 @@
     TYPE_CHIP_DOCS = 4;
     TYPE_SEARCH_HISTORY = 5;
     TYPE_SEARCH_STRING = 6;
+    TYPE_CHIP_LARGE_FILES = 7;
+    TYPE_CHIP_FROM_THIS_WEEK = 8;
 }
 
 enum SearchMode {
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 49c0a29..984be2a 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -33,4 +33,5 @@
     // The human-readable name of the display.
     // Eg: "Built-in Screen"
     optional string name = 5;
+    optional int32 flags = 6;
 }
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 075ebcf..93a9fe2 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -68,4 +68,6 @@
     optional uint32 private_flags = 26;
     optional uint32 system_ui_visibility_flags = 27;
     optional uint32 subtree_system_ui_visibility_flags = 28;
+    optional uint32 appearance = 29;
+    optional uint32 behavior = 30;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9738759..7a0d0cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -643,6 +643,7 @@
     <!-- Grouping for platform runtime permissions is not accessible to apps
          @hide
          @SystemApi
+         @TestApi
     -->
     <permission-group android:name="android.permission-group.UNDEFINED"
         android:priority="100" />
@@ -2008,6 +2009,12 @@
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
+         sessions.
+         @hide Used internally. -->
+    <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Protects the ability to register any PhoneAccount with
          PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
          corresponds to a device SIM.
@@ -2545,6 +2552,19 @@
         android:label="@string/permlab_readSyncStats"
         android:protectionLevel="normal" />
 
+    <!-- ==================================================== -->
+    <!-- Permissions related to accessibility                 -->
+    <!-- ==================================================== -->
+    <eat-comment />
+
+    <!-- Allows applications to define the accessibility shortcut target.
+         <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.ACCESSIBILITY_SHORTCUT_TARGET"
+                android:description="@string/permdesc_accessibilityShortcutTarget"
+                android:label="@string/permlab_accessibilityShortcutTarget"
+                android:protectionLevel="normal" />
+
     <!-- ============================================ -->
     <!-- Permissions for low-level system interaction -->
     <!-- ============================================ -->
diff --git a/core/res/TEST_MAPPING b/core/res/TEST_MAPPING
index 1d22d782..9185bae 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -4,7 +4,10 @@
             "name": "CtsPermission2TestCases",
             "options": [
                 {
-                    "include-filter": "android.permission2.cts.PermissionPolicyTest#testPlatformPermissionPolicyUnaltered"
+                    "include-filter": "android.permission2.cts.PermissionPolicyTest#platformPermissionPolicyIsUnaltered"
+                },
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
                 }
             ]
         }
diff --git a/core/res/res/drawable/media_seamless_background.xml b/core/res/res/drawable/media_seamless_background.xml
new file mode 100644
index 0000000..aec89e0
--- /dev/null
+++ b/core/res/res/drawable/media_seamless_background.xml
@@ -0,0 +1,26 @@
+<?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
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#1f000000">
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <stroke android:width="1dp" android:color="#1f000000"/>
+            <corners android:radius="20dp"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/core/res/res/layout/notification_material_media_transfer_action.xml b/core/res/res/layout/notification_material_media_transfer_action.xml
new file mode 100644
index 0000000..98d8f1e
--- /dev/null
+++ b/core/res/res/layout/notification_material_media_transfer_action.xml
@@ -0,0 +1,39 @@
+<?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="horizontal"
+        android:visibility="gone"
+        android:padding="4dp"
+        android:layout_marginStart="10dp"
+        android:gravity="center"
+        android:background="@drawable/media_seamless_background">
+    <ImageView
+        android:layout_width="?attr/notificationHeaderIconSize"
+        android:layout_height="?attr/notificationHeaderIconSize"
+        android:src="@drawable/ic_media_seamless"
+        android:id="@+id/media_seamless_image" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:text="@string/ext_media_seamless_action"
+        android:id="@+id/media_seamless_text"
+        android:paddingEnd="2dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 0f53549..f5fa1b6a 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -175,5 +175,9 @@
             android:contentDescription="@string/notification_appops_overlay_active"
             />
     </LinearLayout>
+    <include
+        layout="@layout/notification_material_media_transfer_action"
+        android:id="@+id/media_seamless"
+    />
 </NotificationHeaderView>
 
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 3267f72..6c47c2c 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -80,10 +80,6 @@
                 layout="@layout/notification_material_media_action"
                 android:id="@+id/action4"
             />
-            <include
-                layout="@layout/notification_material_media_action"
-                android:id="@+id/media_seamless"
-            />
         </LinearLayout>
         <ViewStub android:id="@+id/notification_media_seekbar_container"
             android:layout_width="match_parent"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index c9500a6..a44b137a 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerke te sien"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koppel"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netwerke"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Laat voorgestelde Wi‑Fi-netwerke toe?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Netwerke wat deur <xliff:g id="NAME">%s</xliff:g> voorgestel is. Toestel sal dalk outomaties koppel."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Laat toe"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nee, dankie"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sal outomaties aanskakel"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer jy naby \'n gestoorde hoëgehaltenetwerk is"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Moenie weer aanskakel nie"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ongekategoriseer"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Jy stel die belangrikheid van hierdie kennisgewings."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dit is belangrik as gevolg van die mense wat betrokke is."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om \'n nuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> te skep?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om \'n nuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> te skep (\'n gebruiker met hierdie rekening bestaan reeds)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Voeg \'n taal by"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Streekvoorkeur"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Voer taalnaam in"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4761db2..db758e3 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ሁሉንም አውታረ መረቦችን ለማየት መታ ያድርጉ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"አገናኝ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ሁሉም አውታረ መረቦች"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"የተጠቆሙ የWi‑Fi አውታረ መረቦች ይፈቀዱ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"በ<xliff:g id="NAME">%s</xliff:g> የተጠቆሙ አውታረ መረቦች። መሣሪያ በራስ-ሰር ሊገናኝ ይችላል።"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ፍቀድ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"አይ፣ አመሰግናለሁ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi በራስ-ሰር ይበራል"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ከፍተኛ ጥራት ያለው የተቀመጠ አውታረ መረብ አቅራቢያ ሲሆኑ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"መልሰህ አታብራ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ያልተመደቡ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"የእነዚህን ማሳወቂያዎች አስፈላጊነት አዘጋጅተዋል።"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ይሄ በሚሳተፉ ሰዎች ምክንያት አስፈላጊ ነው።"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> በ<xliff:g id="ACCOUNT">%2$s</xliff:g> አዲስ ተጠቃሚ እንዲፈጥር ይፈቀድለት?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> በ<xliff:g id="ACCOUNT">%2$s</xliff:g> አዲስ ተጠቃሚ እንዲፈጥር ይፈቀድለት (ይህ መለያ ያለው ተጠቃሚ አስቀድሞ አለ)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ቋንቋ ያክሉ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"የክልል ምርጫ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"የቋንቋ ስም ይተይቡ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c97d2fa..113a342 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -474,7 +474,7 @@
     <string name="permdesc_transmitIr" product="tablet" msgid="5358308854306529170">"للسماح للتطبيق باستخدام مرسل الأشعة تحت الحمراء الخاص بالجهاز اللوحي."</string>
     <string name="permdesc_transmitIr" product="tv" msgid="2752076865253892198">"‏للسماح للتطبيق باستخدام مُرسِل الأشعة تحت الحمراء في جهاز Android TV."</string>
     <string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"للسماح للتطبيق باستخدام مرسل الأشعة تحت الحمراء الخاص بالهاتف."</string>
-    <string name="permlab_setWallpaper" msgid="6627192333373465143">"تعيين الخلفية"</string>
+    <string name="permlab_setWallpaper" msgid="6627192333373465143">"ضبط الخلفية"</string>
     <string name="permdesc_setWallpaper" msgid="7373447920977624745">"للسماح للتطبيق بتعيين خلفية النظام."</string>
     <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"تعديل حجم الخلفية"</string>
     <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"للسماح للتطبيق بتعيين تلميحات حجم خلفية النظام."</string>
@@ -1345,14 +1345,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"انقر للاطلاع على جميع الشبكات"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"جميع الشبكات"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏هل تريد السماح لشبكات Wi‑Fi المقترحة؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"شبكات <xliff:g id="NAME">%s</xliff:g> المقترحة - قد يتم توصيل الجهاز تلقائيًا."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"سماح"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"لا، شكرًا"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏سيتم تشغيل شبكة Wi-Fi تلقائيًا."</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"عندما تكون بالقرب من شبكة محفوظة عالية الجودة"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"عدم إعادة التشغيل"</string>
@@ -2030,8 +2026,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"غير مصنفة"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"لقد عيَّنت أهمية هذه الإشعارات."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"هذه الرسالة مهمة نظرًا لأهمية الأشخاص المعنيين."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"هل تسمح لـ <xliff:g id="APP">%1$s</xliff:g> بإنشاء مستخدم جديد باستخدام <xliff:g id="ACCOUNT">%2$s</xliff:g>؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"هل تسمح لـ <xliff:g id="APP">%1$s</xliff:g> بإنشاء مستخدم جديد باستخدام <xliff:g id="ACCOUNT">%2$s</xliff:g> (يوجد مستخدم بهذا الحساب مسبقًا)؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"إضافة لغة"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"تفضيل المنطقة"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"اكتب اسم اللغة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 2dc3fec..00617a5 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"সকলো নেটৱৰ্ক চাবলৈ টিপক"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"সংযোগ কৰক"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"সকলো নেটৱৰ্ক"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"পৰামর্শ হিচাপে পোৱা নেটৱর্কবোৰক অনুমতি দিবনে?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>এ পৰামর্শ হিচাপে দিয়া নেটৱর্কবোৰ। ডিভাইচটো স্বয়ংক্ৰিয়ভাৱে সংযোগ হ\'ব পাৰে।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"অনুমতি দিয়ক"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"নালাগে, ধন্যবাদ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ৱাই-ফাই স্বয়ংক্ৰিয়ভাৱে অন হ\'ব"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"যেতিয়া আপুনি ছেভ কৰি থোৱা উচ্চ মানৰ নেটৱৰ্কৰ কাষত থাকে"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"পুনৰাই অন নকৰিব"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"শ্ৰেণীবদ্ধ নকৰা"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"এই জাননীবোৰৰ গুৰুত্ব আপুনি ছেট কৰব লাগিব।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"এই কার্যৰ সৈতে জড়িত থকা লোকসকলক ভিত্তি কৰি এইয়া গুৰুত্বপূর্ণ বুলি বিবেচনা কৰা হৈছ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ক <xliff:g id="ACCOUNT">%2$s</xliff:g>ৰ জৰিয়তে নতুন ব্য়ৱহাৰকাৰী সৃষ্টি কৰিবলৈ অনুমতি দিবনে?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>ক <xliff:g id="ACCOUNT">%2$s</xliff:g>ৰ (এই একাউন্টৰ এজন ব্য়ৱহাৰকাৰী ইতিমধ্যে আছে) জৰিয়তে নতুন ব্য়ৱহাৰকাৰী সৃষ্টি কৰিবলৈ অনুমতি দিবনে?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ভাষা যোগ কৰক"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"অঞ্চলৰ অগ্ৰাধিকাৰ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ভাষাৰ নাম লিখক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index af1ca69..f79decd 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Bütün şəbəkələri görmək üçün klikləyin"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Qoşulun"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Bütün şəbəkələr"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Təklif edilən Wi‑Fi şəbəkələrinə icazə verilsin?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> təklif edilən şəbəkə. Cihaz avtomatik qoşula bilər."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"İcazə verin"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Xeyr, təşəkkürlər"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik olaraq aktiv ediləcək"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Yadda saxlanmış yüksək keyfiyyətli şəbəkələr yaxınlıqda olduqda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Yenidən aktiv etməyin"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kateqoriyasız"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bildirişlərin əhəmiyyətini Siz ayarlaryırsınız."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"İnsanlar cəlb olunduğu üçün bu vacibdir."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabı ilə yeni İstifadəçi yaratmağa icazə verilsin?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə<xliff:g id="ACCOUNT">%2$s</xliff:g> (bu hesab ilə İstifadəçi artıq mövcuddur) hesabı ilə yeni İstifadəçi yaratmağa icazə verilsin?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dil əlavə edin"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region seçimi"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Dil adını daxil edin"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 79717fc..10dc339 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da biste videli sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite da dozvolite predložene Wi‑Fi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dozvoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se automatski uključiti"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne uključuj ponovo"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizovano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi podešavate važnost ovih obaveštenja."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ovo je važno zbog ljudi koji učestvuju."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Želite li da dozvolite aplikaciji <xliff:g id="APP">%1$s</xliff:g> da napravi novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Želite li da dozvolite aplikaciji <xliff:g id="APP">%1$s</xliff:g> da napravi novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g> (korisnik sa ovim nalogom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajte jezik"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Podešavanje regiona"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 7647619..ce65bea 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -992,7 +992,7 @@
     </plurals>
     <string name="last_month" msgid="3959346739979055432">"Апошні месяц"</string>
     <string name="older" msgid="5211975022815554840">"Раней"</string>
-    <string name="preposition_for_date" msgid="9093949757757445117">"дата: <xliff:g id="DATE">%s</xliff:g>"</string>
+    <string name="preposition_for_date" msgid="9093949757757445117">"<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="preposition_for_time" msgid="5506831244263083793">"у <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="preposition_for_year" msgid="5040395640711867177">"у <xliff:g id="YEAR">%s</xliff:g>"</string>
     <string name="day" msgid="8144195776058119424">"дзень"</string>
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Дакраніцеся, каб убачыць усе сеткі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Падключыцца"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Усе сеткі"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Дазволіць падключэнне да прапанаваных сетак Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Праграма \"<xliff:g id="NAME">%s</xliff:g>\" прапанавала сеткі. Прылада можа падключыцца да ніх аўтаматычна."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дазволіць"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, дзякуй"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi уключыцца аўтаматычна"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Побач з захаванай сеткай з высакаякасным сігналам"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не ўключаць зноў"</string>
@@ -1393,7 +1389,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"Дазволу не патрабуецца"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"за гэта можа спаганяцца плата"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"ОК"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Зарадка гэтай прылады праз USB"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Прылада зараджаецца праз USB"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"Зарадка падключанай прылады праз USB"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"Перадача файлаў праз USB"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"Перадача фота (PTP) праз USB"</string>
@@ -1724,8 +1720,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> быў адключаны з дапамогай камбінацыі хуткага доступу для спецыяльных магчымасцей"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Каб карыстацца сэрвісам \"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\", націсніце і ўтрымлівайце на працягу трох секунд абедзве клавішы гучнасці"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Выберыце службу для выкарыстання пры націску кнопкі \"Спецыяльныя магчымасці\":"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"З дапамогай жэста спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх) выберыце службу для выкарыстання:"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"З дапамогай жэста спецыяльных магчымасцей (правесці трыма пальцамі па экране знізу ўверх) выберыце службу для выкарыстання:"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберыце службу, дзе будзе выкарыстоўвацца жэст спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберыце службу, дзе будзе выкарыстоўвацца жэст спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Каб пераключыцца на другую службу, націсніце і ўтрымлівайце кнопку спецыяльных магчымасцей."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Каб пераключыцца на другую службу, правядзіце ўверх двума пальцамі, утрымліваючы іх на экране."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Каб пераключыцца на іншую службу, правядзіце ўверх трыма пальцамі, утрымліваючы іх на экране."</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатэгарызаванае"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Вы задалі важнасць гэтых апавяшчэнняў."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Гэта важна, бо з гэтым звязаны пэўныя людзі."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дазволіць <xliff:g id="APP">%1$s</xliff:g> стварыць новага Карыстальніка з уліковым запісам <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дазволіць <xliff:g id="APP">%1$s</xliff:g> стварыць новага Карыстальніка з уліковым запісам <xliff:g id="ACCOUNT">%2$s</xliff:g> (Карыстальнік з гэтым уліковым запісам ужо існуе)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Дадаць мову"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Параметры рэгіёна"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Увядзіце назву мовы"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2c6550d..87ea6de 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризирани"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Зададохте важността на тези известия."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Това е важно заради участващите хора."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Да се разреши ли на <xliff:g id="APP">%1$s</xliff:g> да създаде нов потребител с профила <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Да се разреши ли на <xliff:g id="APP">%1$s</xliff:g> да създаде нов потребител с профила <xliff:g id="ACCOUNT">%2$s</xliff:g> (вече съществува потребител с този профил)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Добавяне на език"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Предпочитание за региона"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Въведете име на език"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index c2d501c..c0843ea 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"এই অ্যাপ আপনার শারীরিক অ্যাক্টিভিটি শনাক্ত করতে পারবে।"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ছবি এবং ভিডিও তোলে"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"এই অ্যাপটি যে কোনো সময় ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারে৷"</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"সিস্টেম ক্যামেরা ব্যবহার করে ফটো এবং ভিডিও নেওয়ার জন্য অ্যাপ্লিকেশন বা পরিষেবা অ্যাক্সেসের অনুমতি দিন"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"এই প্রিভিলিজ | সিস্টেম অ্যাপটি যেকোনও সময়ে সিস্টেম ক্যামেরা ব্যবহার করে ছবি তুলতে এবং ভিডিও রেকর্ড করতে পারবে। এর জন্য অ্যাপের Android.permission.CAMERA -এর অনুমতি প্রয়োজন"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"ভাইব্রেশন নিয়ন্ত্রণ করুন"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"অ্যাপ্লিকেশানকে কম্পক নিয়ন্ত্রণ করতে দেয়৷"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"সরাসরি ফোন নম্বরগুলিতে কল করে"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"সমস্ত নেটওয়ার্ক দেখতে ট্যাপ করুন"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"সংযুক্ত করুন"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"সব নেটওয়ার্ক"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"সাজেস্ট করা ওয়াই-ফাই নেটওয়ার্কে কানেক্ট করার অনুমতি দিতে চান?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>-এর সাজেস্ট করা নেটওয়ার্ক। ডিভাইস নিজে থেকে কানেক্ট হতে পারে।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"অনুমতি দিন"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"না থাক"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ওয়াই-ফাই অটোমেটিক চালু হবে"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"যখন আপনি একটি উচ্চ মানের সংরক্ষিত নেটওয়ার্ক কাছাকাছি থাকেন"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"আবার চালু করবেন না"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"বিভাগ নির্ধারিত নয়"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"আপনি এই বিজ্ঞপ্তিগুলির গুরুত্ব সেট করেছেন।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"লোকজন জড়িত থাকার কারণে এটি গুরুত্বপূর্ণ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> কে <xliff:g id="ACCOUNT">%2$s</xliff:g> এর সাথে একজন নতুন ব্যবহারকারী তৈরি করার অনুমতি দেবেন কি?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> কে <xliff:g id="ACCOUNT">%2$s</xliff:g> (একজন ব্যবহারকারী এই অ্যাকাউন্টে ইতিমধ্যেই বিদ্যমান আছেন) এর সাথে একজন নতুন ব্যবহারকারী তৈরি করার অনুমতি দেবেন কি?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"একটি ভাষা যোগ করুন"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"পছন্দের অঞ্চল"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ভাষার নাম লিখুন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b28774b..0d4b12c 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -295,7 +295,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da pristupi vašem kalendaru?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"šalje i pregleda SMS poruke"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; slanje i pregled SMS poruka?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da šalje i pregleda SMS poruke?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Pohrana"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"pristupa slikama, medijskim fajlovima i fajlovima na vašem uređaju"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da pristupa fotografijama, medijima i fajlovima na vašem uređaju?"</string>
@@ -1281,14 +1281,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da vidite sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Povežite se"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Dozvoliti predložene WiFi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dozvoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WiFi će se uključiti automatski"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nemoj ponovo uključiti"</string>
@@ -1384,7 +1380,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Punjenje povezanog uređaja. Dodirnite za više opcija."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Otkriven je analogni periferni uređaj"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Priključeni uređaj nije kompatibilan s ovim telefonom. Dodirnite da saznate više."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka putem uređaja spojenog na USB je uspostavljeno"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka putem USB-a je uspostavljeno"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"Dodirnite da isključite otklanjanje grešaka putem USB-a"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Odaberite da onemogućite ispravljanje grešaka koristeći USB"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Omogućen način rada okvira za testiranje"</string>
@@ -1930,8 +1926,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nije kategorizirano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi određujete značaj ovih obavještenja."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ovo je značajno zbog osoba koje su uključene."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Da li dozvoljavate aplikaciji <xliff:g id="APP">%1$s</xliff:g> da kreira novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Da li dozvoljavate da <xliff:g id="APP">%1$s</xliff:g> kreira novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g> (Korisnik sa ovim nalogom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajte jezik"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Izbor regije"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Upišite ime jezika"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 6d83936..d828844 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca per veure totes les xarxes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connecta"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Totes les xarxes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vols permetre les xarxes Wi‑Fi suggerides?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Xarxes suggerides de l\'aplicació <xliff:g id="NAME">%s</xliff:g>. El dispositiu pot connectar-se automàticament."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permet"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gràcies"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La Wi-Fi s\'activarà automàticament"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quan siguis a prop d\'una xarxa de qualitat desada"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No tornis a activar"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sense classificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Has definit la importància d\'aquestes notificacions."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Aquest missatge és important per les persones implicades."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Concedeixes permís a <xliff:g id="APP">%1$s</xliff:g> per crear un usuari amb el compte <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Concedeixes permís a <xliff:g id="APP">%1$s</xliff:g> per crear un usuari amb el compte <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Ja hi ha un usuari amb aquest compte.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Afegeix un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferència de regió"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nom de l\'idioma"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 0069804..2b524ae 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všechny sítě"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Připojit"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Všechny sítě"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Povolit navrhované sítě Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Sítě navrhované aplikací <xliff:g id="NAME">%s</xliff:g>. Zařízení se může připojovat automaticky."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Povolit"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, díky"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se zapne automaticky"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Když budete v dosahu kvalitní uložené sítě"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znovu nezapínat"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Neklasifikováno"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Důležitost oznámení určujete vy."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tato zpráva je důležitá kvůli lidem zapojeným do konverzace."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> vytvořit nového uživatele s účtem <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> vytvořit nového uživatele s účtem <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Uživatel s tímto účtem již existuje.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Přidat jazyk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferovaná oblast"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Zadejte název jazyka"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d12a794..4d57a90 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryk for at se alle netværk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Opret forbindelse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netværk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vil du tillade foreslåede Wi‑Fi-netværk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Netværk foreslået af <xliff:g id="NAME">%s</xliff:g>. Enheden opretter muligvis forbindelse automatisk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillad"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nej tak"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi aktiveres automatisk"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheden af et gemt netværk af høj kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Aktivér ikke igen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uden kategori"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du angiver, hvor vigtige disse notifikationer er."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dette er vigtigt på grund af de personer, det handler om."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vil du give <xliff:g id="APP">%1$s</xliff:g> tilladelse til at oprette en ny bruger med <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vil du give <xliff:g id="APP">%1$s</xliff:g> tilladelse til at oprette en ny bruger med <xliff:g id="ACCOUNT">%2$s</xliff:g> (der findes allerede en bruger med denne konto)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tilføj et sprog"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Områdeindstilling"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Angiv sprog"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f7b66a1..2ec70d3 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf deine Kontakte zugreift?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Standort"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"auf den Standort deines Geräts zugreifen"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; den Gerätestandort abruft?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"Zulassen, dass die App &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; den Gerätestandort abruft?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"Die App hat nur Zugriff auf den Gerätestandort, solange du sie verwendest"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; &lt;b&gt;ständig&lt;/b&gt; auf deinen Standort zugreift?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"Die App hat gegenwärtig nur dann Zugriff auf den Gerätestandort, wenn du sie verwendest"</string>
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf deinen Kalender zugreift?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS senden und abrufen"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; SMS sendet und aufruft?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Zulassen, dass die App &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; SMS sendet und aufruft?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Speicher"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"auf Fotos, Medien und Dateien auf deinem Gerät zugreifen"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf Fotos, Medien und Dateien auf deinem Gerät zugreift?"</string>
@@ -434,8 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"Diese App kann deine körperliche Aktivität erkennen."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"Diese App kann mit der Kamera jederzeit Bilder und Videos aufnehmen."</string>
-    <string name="permlab_systemCamera" msgid="4074081285026193898">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufzunehmen"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Diese privilegierte System-App kann jederzeit mit einer System-Kamera Bilder und Videos aufnehmen. Sie benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string>
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufnehmen zu können"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Diese privilegierte App | System-App kann jederzeit mit einer Systemkamera Bilder und Videos aufnehmen. Die App benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"Vibrationsalarm steuern"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Ermöglicht der App, den Vibrationsalarm zu steuern"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"Telefonnummern direkt anrufen"</string>
@@ -999,7 +999,7 @@
     <string name="weeks" msgid="6509623834583944518">"Wochen"</string>
     <string name="year" msgid="4001118221013892076">"Jahr"</string>
     <string name="years" msgid="6881577717993213522">"Jahre"</string>
-    <string name="now_string_shortest" msgid="8912796667087856402">"jetzt"</string>
+    <string name="now_string_shortest" msgid="8912796667087856402">"Jetzt"</string>
     <plurals name="duration_minutes_shortest" formatted="false" msgid="3957499975064245495">
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tippen, um alle Netzwerke zu sehen"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinden"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle Netzwerke"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vorgeschlagene WLANs zulassen?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Von <xliff:g id="NAME">%s</xliff:g> vorgeschlagene Netzwerke. Gerät verbindet sich möglicherweise automatisch."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Zulassen"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nein danke"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN wird automatisch aktiviert"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wenn du in der Nähe eines sicheren gespeicherten Netzwerks bist"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nicht wieder aktivieren"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Unkategorisiert"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du hast die Wichtigkeit dieser Benachrichtigungen festgelegt."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Diese Benachrichtigung ist aufgrund der beteiligten Personen wichtig."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Möchtest du zulassen, dass <xliff:g id="APP">%1$s</xliff:g> einen neuen Nutzer mit <xliff:g id="ACCOUNT">%2$s</xliff:g> erstellt?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Möchtest du zulassen, dass <xliff:g id="APP">%1$s</xliff:g> einen neuen Nutzer mit <xliff:g id="ACCOUNT">%2$s</xliff:g> erstellt? Dieses Konto wird jedoch bereits von einem anderen Nutzer verwendet."</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Sprache hinzufügen"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region auswählen"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sprache eingeben"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 30824c8..6998c0c 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Πατήστε για να δείτε όλα τα δίκτυα"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Σύνδεση"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Όλα τα δίκτυα"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Να επιτρέπονται προτεινόμενα δίκτυα Wi‑Fi;"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Προτεινόμενα δίκτυα <xliff:g id="NAME">%s</xliff:g>. Η συσκευή μπορεί να συνδεθεί αυτόματα."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Αποδοχή"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Όχι, ευχαριστώ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Το Wi‑Fi θα ενεργοποιηθεί αυτόματα"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Όταν βρίσκεστε κοντά σε αποθηκευμένο δίκτυο υψηλής ποιότητας"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Να μην ενεργοποιηθεί ξανά"</string>
@@ -1679,7 +1675,7 @@
     <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Επιλέξτε μια υπηρεσία που θα χρησιμοποιείται με την κίνηση προσβασιμότητας (σύρετε με δύο δάχτυλα προς τα επάνω από το κάτω μέρος της οθόνης):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Επιλέξτε μια υπηρεσία που θα χρησιμοποιείται με την κίνηση προσβασιμότητας (σύρετε με τρία δάχτυλα προς τα επάνω από το κάτω μέρος της οθόνης):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Για εναλλαγή μεταξύ υπηρεσιών, αγγίξτε παρατεταμένα το κουμπί προσβασιμότητας."</string>
-    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με δύο δάχτυλα προς τα επάνω."</string>
+    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με δύο δάχτυλα προς τα επάνω και μην τα απομακρύνετε από την οθόνη."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με τρία δάχτυλα προς τα επάνω."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Μεγιστοποίηση"</string>
     <string name="user_switched" msgid="3768006783166984410">"Τρέχων χρήστης <xliff:g id="NAME">%1$s</xliff:g>."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Μη κατηγοριοποιημένο"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Μπορείτε να ρυθμίσετε τη βαρύτητα αυτών των ειδοποιήσεων."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Αυτό είναι σημαντικό λόγω των ατόμων που συμμετέχουν."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Να επιτραπεί στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με το λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g>;"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Να επιτραπεί στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με το λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> (υπάρχει ήδη χρήστης με αυτόν το λογαριασμό);"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Προσθήκη γλώσσας"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Προτίμηση περιοχής"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Εισαγ. όνομα γλώσσας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 839aaac..6da3a5f 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 7961a86..62f294e 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎Uncategorized‎‏‎‎‏‎"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎You set the importance of these notifications.‎‏‎‎‏‎"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎This is important because of the people involved.‎‏‎‎‏‎"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to create a new User with ‎‏‎‎‏‏‎<xliff:g id="ACCOUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎ ?‎‏‎‎‏‎"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to create a new User with ‎‏‎‎‏‏‎<xliff:g id="ACCOUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎ (a User with this account already exists) ?‎‏‎‎‏‎"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎Add a language‎‏‎‎‏‎"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎Region preference‎‏‎‎‏‎"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎Type language name‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ac6a6ce..7346bd0 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -30,7 +30,7 @@
     <string name="untitled" msgid="4638956954852782576">"&lt;Sin título&gt;"</string>
     <string name="emptyPhoneNumber" msgid="7694063042079676517">"(No hay número de teléfono)"</string>
     <string name="unknownName" msgid="6867811765370350269">"Desconocido"</string>
-    <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string>
+    <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string>
     <string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string>
@@ -944,7 +944,7 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los favoritos del navegador almacenados en el dispositivo. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"programar una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
-    <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
+    <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar buzón de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Presiona para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas las redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"¿Quieres permitir las redes Wi‑Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> sugirió redes. Es posible que el dispositivo se conecte automáticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gracias"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Se activará la conexión Wi-Fi automáticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red guardada de alta calidad"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sin categoría"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Estableciste la importancia de estas notificaciones."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Es importante debido a las personas involucradas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"¿Quieres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario nuevo con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"¿Quieres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario nuevo con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Ya existe un usuario con esta cuenta)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Agregar un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nombre del idioma"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 059b0a20..6c1c992 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas las redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"¿Permitir sugerencias de redes Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> te ha sugerido alguna red. El dispositivo puede que se conecte automáticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gracias"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La conexión Wi‑Fi se activará automáticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red de alta calidad guardada"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Se ha detectado un accesorio de audio analógico"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"El dispositivo adjunto no es compatible con este teléfono. Toca para obtener más información."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración USB habilitada"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toca para desactivar la depuración USB."</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Tocar para desactivar depuración USB"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleccionar para inhabilitar la depuración USB"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Modo de agente de prueba habilitado"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Restablece los ajustes de fábrica para inhabilitar el modo de agente de prueba."</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"El acceso directo a accesibilidad ha desactivado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Para utilizar <xliff:g id="SERVICE_NAME">%1$s</xliff:g>, mantén pulsadas ambas teclas de volumen durante 3 segundos"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Selecciona el servicio que se utilizará cuando toques el botón Accesibilidad:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Elige el servicio que se utilizará con el gesto de accesibilidad (desliza dos dedos hacia arriba desde la parte inferior de la pantalla):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Elige el servicio que se utilizará con el gesto de accesibilidad (desliza tres dedos hacia arriba desde la parte inferior de la pantalla):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Elige el servicio que se utilizará con el gesto de accesibilidad (deslizar dos dedos hacia arriba desde la parte inferior de la pantalla):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Elige el servicio que se utilizará con el gesto de accesibilidad (deslizar tres dedos hacia arriba desde la parte inferior de la pantalla):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para cambiar de un servicio a otro, mantén pulsado el botón de accesibilidad."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para cambiar de un servicio a otro, desliza dos dedos hacia arriba y mantén pulsada la pantalla."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para cambiar de un servicio a otro, desliza tres dedos hacia arriba y mantén pulsada la pantalla."</string>
@@ -1894,9 +1890,11 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sin clasificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Tú determinas la importancia de estas notificaciones."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Esto es importante por los usuarios implicados."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"¿Permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario con la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"¿Permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario con la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> (ya existe un usuario con esta cuenta)?"</string>
-    <string name="language_selection_title" msgid="2680677278159281088">"Añade un idioma"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
+    <string name="language_selection_title" msgid="2680677278159281088">"Añadir un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nombre de idioma"</string>
     <string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index f7601fd..28d1203 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -243,7 +243,7 @@
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Hääletu režiim"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Heli on VÄLJAS"</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Heli on SEES"</string>
-    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Lennurežiim"</string>
+    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Lennukirežiim"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Lennurežiim on SEES"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Lennurežiim on VÄLJAS"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Seaded"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Puudutage kõikide võrkude nägemiseks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ühenda"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Kõik võrgud"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Kas lubada soovitatud WiFi-võrgud?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rakenduse <xliff:g id="NAME">%s</xliff:g> soovitatud võrgud. Seade võib automaatselt ühenduse luua."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Luba"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Tänan, ei"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WiFi lülitub sisse automaatselt"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kui olete kvaliteetse salvestatud võrgu läheduses"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ära lülita tagasi sisse"</string>
@@ -1676,7 +1672,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Juurdepääsetavuse otsetee lülitas teenuse <xliff:g id="SERVICE_NAME">%1$s</xliff:g> välja"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Teenuse <xliff:g id="SERVICE_NAME">%1$s</xliff:g> kasutamiseks hoidke kolm sekundit all mõlemat helitugevuse klahvi"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Valige, millist teenust kasutada, kui puudutate juurdepääsetavuse nuppu:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (pühkige kahe sõrmega ekraanikuval alt üles):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (kahe sõrmega ekraanikuval alt üles pühkimine):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (kolme sõrmega ekraanikuval alt üles pühkimine):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Teenuste vahel vahetamiseks vajutage pikalt juurdepääsetavuse nuppu."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Teenuste vahel vahetamiseks pühkige kahe sõrmega üles ja hoidke."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategoriseerimata"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Teie määrasite nende märguannete tähtsuse."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"See on tähtis osalevate inimeste tõttu."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Kas lubada rakendusel <xliff:g id="APP">%1$s</xliff:g> luua uus kasutaja kontoga <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Kas lubada rakendusel <xliff:g id="APP">%1$s</xliff:g> luua uus kasutaja kontoga <xliff:g id="ACCOUNT">%2$s</xliff:g> (selle kontoga kasutaja on juba olemas)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Keele lisamine"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Piirkonnaeelistus"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sisestage keele nimi"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2cfda07..9460ee7 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Sakatu hau sare guztiak ikusteko"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Konektatu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sare guztiak"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Iradokitako wifi-sareak baimendu nahi dituzu?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> aplikazioak sare batzuk iradoki ditu. Baliteke gailua automatikoki konektatzea."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Baimendu"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ez. Eskerrik asko."</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi konexioa automatikoki aktibatuko da"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gordeta daukazun kalitate handiko sare batetik gertu zaudenean"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ez aktibatu berriro"</string>
@@ -1642,7 +1638,7 @@
     <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN kode okerra."</string>
     <string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Idatzi 4 eta 8 zenbaki arteko PINa."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6025069204539532000">"PUK kodeak 8 zenbaki izan behar ditu."</string>
-    <string name="kg_invalid_puk" msgid="3638289409676051243">"Idatzi berriro PUK kode zuzena. Hainbat saiakera oker eginez gero, betirako desgaituko da SIMa."</string>
+    <string name="kg_invalid_puk" msgid="3638289409676051243">"Idatzi berriro PUK kode zuzena. Hainbat saiakera oker eginez gero, betiko desgaituko da SIMa."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodeak ez datoz bat"</string>
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Eredua marrazteko saiakera gehiegi egin dira"</string>
     <string name="kg_login_instructions" msgid="1100551261265506448">"Desblokeatzeko, hasi saioa Google kontuarekin."</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategoriarik gabea"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Zuk ezarri duzu jakinarazpen hauen garrantzia."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Garrantzitsua da eragiten dien pertsonengatik."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> aplikazioari <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua duen erabiltzailea sortzeko baimena eman nahi diozu?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> aplikazioari <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua duen erabiltzailea sortzeko baimena eman nahi diozu? (Badago kontu hori duen erabiltzaile bat)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Gehitu hizkuntza"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Lurralde-hobespena"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Adierazi hizkuntza"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index c620ab2..ec5dbcb2 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه داده شود به تقویم شما دسترسی پیدا کند؟"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"پیامک"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"ارسال و مشاهده پیامک‌ها"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; اجازه داده شود پیامک‌ها را ارسال و مشاهده کند؟"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"‏به «&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt;» اجازه داده شود پیامک ارسال و مشاهده کند؟"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"حافظه"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"دسترسی به عکس‌ها، رسانه‌ها و فایل‌های روی دستگاهتان"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"‏به برنامه &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; اجازه داده شود به عکس‌ها، رسانه، و فایل‌های موجود در دستگاهتان دسترسی داشته باشد؟"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"برای دیدن همه شبکه‌ها ضربه بزنید"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"همه شبکه‌ها"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏شبکه‌های Wi‑Fi پیشنهادی مجاز شود؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"شبکه‌های پیشنهادی <xliff:g id="NAME">%s</xliff:g>. ممکن است دستگاه به‌طور خودکار متصل شود."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"مجاز"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"نه متشکرم"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi به‌طور خودکار روشن خواهد شد"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"وقتی نزدیک شبکه ذخیره‌شده با کیفیت بالا هستید"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوباره روشن نشود"</string>
@@ -1814,8 +1810,8 @@
     <string name="package_updated_device_owner" msgid="1847154566357862089">"توسط سرپرست سیستم به‌روزرسانی شد"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"توسط سرپرست سیستم حذف شد"</string>
     <string name="confirm_battery_saver" msgid="639106420541753635">"تأیید"</string>
-    <string name="battery_saver_description_with_learn_more" msgid="2108984221113106294">"بهینه‌سازی باتری فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های با مصرف بالای نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد. "<annotation id="url">"بیشتر بدانید"</annotation></string>
-    <string name="battery_saver_description" msgid="6413346684861241431">"بهینه‌سازی باتری فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های با مصرف بالای نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد."</string>
+    <string name="battery_saver_description_with_learn_more" msgid="2108984221113106294">"«بهینه‌سازی باتری» فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های پرمصرف نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد. "<annotation id="url">"بیشتر بدانید"</annotation></string>
+    <string name="battery_saver_description" msgid="6413346684861241431">"«بهینه‌سازی باتری» فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های پرمصرف نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد."</string>
     <string name="data_saver_description" msgid="6015391409098303235">"برای کمک به کاهش مصرف داده، «صرفه‌جویی داده» از ارسال و دریافت داده در پس‌زمینه ازطرف بعضی برنامه‌ها جلوگیری می‌کند. برنامه‌ای که درحال‌حاضر استفاده می‌کنید می‌تواند به داده‌ها دسترسی داشته باشد اما دفعات دسترسی آن محدود است.این یعنی، برای مثال، تصاویر تا زمانی که روی آن‌ها ضربه نزنید نشان داده نمی‌شوند."</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"صرفه‌جویی داده روشن شود؟"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"روشن کردن"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"دسته‌بندی‌نشده"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"شما اهمیت این اعلان‌ها را تنظیم می‌کنید."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"به دلیل افراد درگیر مهم است."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"به <xliff:g id="APP">%1$s</xliff:g> امکان داده شود کاربر جدیدی با <xliff:g id="ACCOUNT">%2$s</xliff:g> اضافه کند؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"به <xliff:g id="APP">%1$s</xliff:g> امکان داده شود کاربر جدیدی با <xliff:g id="ACCOUNT">%2$s</xliff:g> ایجاد کند (کاربری با این حساب از قبل وجود دارد)؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"افزودن زبان"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"اولویت‌های منطقه"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"نام زبان را تایپ کنید"</string>
@@ -2010,15 +2008,15 @@
     <string name="notification_appops_overlay_active" msgid="633813008357934729">"نمایش روی برنامه‌های دیگر در صفحه‌نمایش"</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2348803891571320452">"اعلان اطلاعات حالت روال معمول"</string>
     <string name="dynamic_mode_notification_title" msgid="508815255807182035">"ممکن است شارژ باتری قبل از شارژ معمول تمام شود"</string>
-    <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"جهت افزایش عمر باتری، بهینه‌سازی باتری فعال شد"</string>
+    <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"جهت افزایش عمر باتری، «بهینه‌سازی باتری» فعال شد"</string>
     <string name="battery_saver_notification_channel_name" msgid="2083316159716201806">"بهینه‌سازی باتری"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="6376147579378764641">"تا وقتی شارژ باتری دوباره به سطح پایین نرسد، بهینه‌سازی باتری مجدداً فعال نخواهد شد"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="8090192609249817945">"باتری تا سطحی کافی شارژ شده است. تا وقتی شارژ باتری دوباره به سطح پایین نرسد، بهینه‌سازی باتری مجدداً فعال نخواهد شد."</string>
+    <string name="battery_saver_sticky_disabled_notification_title" msgid="6376147579378764641">"تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد"</string>
+    <string name="battery_saver_sticky_disabled_notification_summary" msgid="8090192609249817945">"باتری درحد کافی شارژ شده است. تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد."</string>
     <string name="battery_saver_charged_notification_title" product="default" msgid="2960978289873161288">"تلفن <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
     <string name="battery_saver_charged_notification_title" product="tablet" msgid="7555713825806482451">"رایانه لوحی <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
     <string name="battery_saver_charged_notification_title" product="device" msgid="5954873381559605660">"دستگاه <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
-    <string name="battery_saver_off_notification_summary" msgid="1374222493681267143">"بهینه‌سازی باتری خاموش است. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="4340727818546508436">"بهینه‌سازی باتری خاموش شد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
+    <string name="battery_saver_off_notification_summary" msgid="1374222493681267143">"«بهینه‌سازی باتری» خاموش است. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
+    <string name="battery_saver_off_alternative_notification_summary" msgid="4340727818546508436">"«بهینه‌سازی باتری» خاموش شد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
     <string name="mime_type_folder" msgid="7111951698626315204">"پوشه"</string>
     <string name="mime_type_apk" msgid="5518003630972506900">"‏برنامه Android"</string>
     <string name="mime_type_generic" msgid="6833871596845900027">"فایل"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5b934ea3..a94253b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; yhteystietojesi käyttöoikeuden?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Sijainti"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"käyttää laitteen sijaintia"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tämän laitteen sijainnin käyttöoikeuden?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; oikeuden nähdä tämän laitteen sijainnin?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"Sovellus saa sijainnin käyttöoikeuden vain silloin, kun käytät sovellusta"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; käyttää laitteen sijaintia &lt;b&gt;aina&lt;/b&gt;?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"Sovellus saa tällä hetkellä sijainnin käyttöoikeuden vain, jos käytät sovellusta"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Napauta, niin näet kaikki verkot."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Yhdistä"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Kaikki verkot"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Sallitaanko ehdotetut Wi-Fi-verkot?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ehdotti verkkoja. Laite voi muodostaa yhteyden automaattisesti."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Salli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ei kiitos"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi käynnistyy automaattisesti"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kun olet lähellä laadukasta tallennettua verkkoa"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Älä käynnistä uudelleen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Luokittelematon"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Voit valita näiden ilmoitusten tärkeyden."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tämä on tärkeää siihen liittyvien ihmisten perusteella."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Myönnetäänkö sovellukselle <xliff:g id="APP">%1$s</xliff:g> oikeus luoda käyttäjä tilille <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Myönnetäänkö sovellukselle <xliff:g id="APP">%1$s</xliff:g> oikeus luoda käyttäjä tilille <xliff:g id="ACCOUNT">%2$s</xliff:g> (tilillä on jo käyttäjä)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Lisää kieli"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Alueasetus"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Anna kielen nimi"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index b4dfde6..94f6acb 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -100,7 +100,7 @@
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Mode TTY HCO demandé par un pair"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Mode TTY VCO demandé par un pair"</string>
     <string name="peerTtyModeOff" msgid="3280819717850602205">"Mode TTY DÉSACTIVÉ demandé par un pair"</string>
-    <string name="serviceClassVoice" msgid="1258393812335258019">"Google Voice"</string>
+    <string name="serviceClassVoice" msgid="1258393812335258019">"Voix"</string>
     <string name="serviceClassData" msgid="872456782077937893">"Données"</string>
     <string name="serviceClassFAX" msgid="5566624998840486475">"Télécopie"</string>
     <string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Touchez pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connexion"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tous les réseaux"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Autoriser les suggestions de réseaux Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil peut s\'y connecter automatiquement."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Autoriser"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non merci"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi s\'activera automatiquement"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes près d\'un réseau enregistré de haute qualité"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
@@ -1676,10 +1672,10 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Le raccourci d\'accessibilité a désactivé la fonction <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Maintenez enfoncées les deux touches de volume pendant trois secondes pour utiliser <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Choisissez un service à utiliser lorsque vous touchez le bouton d\'accessibilité :"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer deux doigts du bas de l\'écran vers le haut) :"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer trois doigts du bas de l\'écran vers le haut) :"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer l\'écran de bas en haut avec deux doigts) :"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer l\'écran de bas en haut avec trois doigts) :"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Pour basculer entre les services, maintenez le doigt sur le bouton d\'accessibilité."</string>
-    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Pour basculer entre les services, balayez deux doigts vers le haut et maintenez-les sur l\'écran."</string>
+    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Pour basculer entre les services, balayez l\'écrfan vers le haut avec deux doigts et maintenez-les-y."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Pour changer de service, balayez trois doigts vers le haut et maintenez-les sur l\'écran."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Zoom"</string>
     <string name="user_switched" msgid="3768006783166984410">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sans catégorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vous définissez l\'importance de ces notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ces notifications sont importantes en raison des participants."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil d\'utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil d\'utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Un utilisateur associé à ce compte existe déjà.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ajouter une langue"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Préférences régionales"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Entrez la langue"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 33d13dc..11ba7dc 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; d\'accéder à votre agenda ?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"envoyer et consulter des SMS"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; d\'envoyer et d\'afficher des SMS ?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Autoriser l\'application &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à envoyer et afficher des SMS ?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Stockage"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"accéder aux photos, contenus multimédias et fichiers sur votre appareil"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Autoriser l\'appli &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux photos, contenus multimédias et fichiers sur votre appareil ?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Appuyer pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Se connecter"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tous les réseaux"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Autoriser les suggestions de réseaux Wi‑Fi ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil pourra se connecter automatiquement."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Autoriser"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non, merci"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi sera activé automatiquement"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes à proximité d\'un réseau enregistré de haute qualité"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sans catégorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vous définissez l\'importance de ces notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ces notifications sont importantes en raison des participants."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g> (un utilisateur associé à ce compte existe déjà) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ajouter une langue"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Préférences régionales"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Saisissez la langue"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index f977c45..1becada 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1094,27 +1094,27 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de introdución de texto"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
-    <string name="email_desc" msgid="3638665569546416795">"Envía un correo electrónico ao enderezo seleccionado"</string>
+    <string name="email_desc" msgid="3638665569546416795">"Enviar un correo electrónico ao enderezo seleccionado"</string>
     <string name="dial" msgid="1253998302767701559">"Chamar"</string>
-    <string name="dial_desc" msgid="6573723404985517250">"Chama ao número de teléfono seleccionado"</string>
+    <string name="dial_desc" msgid="6573723404985517250">"Chamar ao número de teléfono seleccionado"</string>
     <string name="map" msgid="5441053548030107189">"Mapa"</string>
-    <string name="map_desc" msgid="1836995341943772348">"Localiza o enderezo seleccionado"</string>
+    <string name="map_desc" msgid="1836995341943772348">"Localizar o enderezo seleccionado"</string>
     <string name="browse" msgid="1245903488306147205">"Abrir"</string>
-    <string name="browse_desc" msgid="8220976549618935044">"Abre o URL seleccionado"</string>
+    <string name="browse_desc" msgid="8220976549618935044">"Abrir o URL seleccionado"</string>
     <string name="sms" msgid="4560537514610063430">"Enviar SMS"</string>
-    <string name="sms_desc" msgid="7526588350969638809">"Envía unha mensaxe ao número de teléfono seleccionado"</string>
+    <string name="sms_desc" msgid="7526588350969638809">"Enviar unha mensaxe ao número de teléfono seleccionado"</string>
     <string name="add_contact" msgid="7867066569670597203">"Engadir"</string>
-    <string name="add_contact_desc" msgid="4830217847004590345">"Engade o elemento aos contactos"</string>
+    <string name="add_contact_desc" msgid="4830217847004590345">"Engadir o elemento aos contactos"</string>
     <string name="view_calendar" msgid="979609872939597838">"Ver"</string>
-    <string name="view_calendar_desc" msgid="5828320291870344584">"Consulta a hora seleccionada no calendario"</string>
+    <string name="view_calendar_desc" msgid="5828320291870344584">"Consultar a hora seleccionada no calendario"</string>
     <string name="add_calendar_event" msgid="1953664627192056206">"Programar"</string>
-    <string name="add_calendar_event_desc" msgid="4326891793260687388">"Programa un evento para a data seleccionada"</string>
+    <string name="add_calendar_event_desc" msgid="4326891793260687388">"Programar un evento para a data seleccionada"</string>
     <string name="view_flight" msgid="7691640491425680214">"Realizar seguimento"</string>
-    <string name="view_flight_desc" msgid="3876322502674253506">"Fai un seguimento do voo seleccionado"</string>
+    <string name="view_flight_desc" msgid="3876322502674253506">"Facer un seguimento do voo seleccionado"</string>
     <string name="translate" msgid="9218619809342576858">"Traducir"</string>
-    <string name="translate_desc" msgid="4502367770068777202">"Traduce o texto seleccionado"</string>
+    <string name="translate_desc" msgid="4502367770068777202">"Traducir o texto seleccionado"</string>
     <string name="define" msgid="7394820043869954211">"Definir"</string>
-    <string name="define_desc" msgid="7910883642444919726">"Define o texto seleccionado"</string>
+    <string name="define_desc" msgid="7910883642444919726">"Definir o texto seleccionado"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Estase esgotando o espazo de almacenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"É posible que algunhas funcións do sistema non funcionen"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Non hai almacenamento suficiente para o sistema. Asegúrate de ter un espazo libre de 250 MB e reinicia o dispositivo."</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Queres permitir as redes wifi suxeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes suxeridas de <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode conectarse automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non, grazas"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A wifi activarase automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cando esteas preto dunha rede gardada de alta calidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non volver activar"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sen clasificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ti defines a importancia destas notificacións."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"É importante polas persoas involucradas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Queres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario novo con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Queres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario novo con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Xa existe un usuario con esta conta)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Engadir un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de rexión"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nome do idioma"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 83e94a1..09661c1 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને તમારા સંપર્કોને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"સ્થાન"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"આ ઉપકરણના સ્થાનને ઍક્સેસ કરવાની"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ઉપકરણના સ્થાનને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસના સ્થાનને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"જ્યારે તમે ઍપનો ઉપયોગ કરી રહ્યા હશો માત્ર ત્યારે જ ઍપ સ્થાનને ઍક્સેસ કરી શકશે"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસનું સ્થાન &lt;b&gt;હંમેશાં&lt;/b&gt; ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"હાલમાં માત્ર જ્યારે તમે ઍપનો ઉપયોગ કરી રહ્યા હશો હોય ત્યારે જ ઍપ સ્થાનને ઍક્સેસ કરી શકશે"</string>
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"આ ઍપ તમારી શારીરિક પ્રવૃત્તિને ઓળખી શકે છે."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ચિત્રો અને વિડિઓઝ લો"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"આ ઍપ્લિકેશન, કૅમેરાનો ઉપયોગ કરીને કોઈપણ સમયે ચિત્રો લઈ અને વિડિઓઝ રેકોર્ડ કરી શકે છે."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ઍપ્લિકેશન અથવા સેવા ઍક્સેસને સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા અને વીડિયો લેવાની મંજૂરી આપો"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"આ વિશેષાધિકૃત | સિસ્ટમ ઍપ કોઈપણ સમયે સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા લઈ અને વીડિયો રેકોર્ડ કરી શકે છે. ઍપ દ્વારા આયોજિત કરવા માટે android.permission.CAMERAની પરવાનગી પણ જરૂરી છે"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"વાઇબ્રેશન નિયંત્રિત કરો"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"એપ્લિકેશનને વાઇબ્રેટરને નિયંત્રિત કરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"સીધા જ ફોન નંબર્સ પર કૉલ કરો"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"બધા નેટવર્ક જોવા ટૅપ કરો"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"કનેક્ટ કરો"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"બધા નેટવર્કો"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"સૂચવેલા વાઇ-ફાઇ નેટવર્કને મંજૂરી આપીએ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> સૂચવેલા નેટવર્ક. ડિવાઇસ ઑટોમૅટિક રીતે કનેક્ટ થાય તેમ બની શકે છે."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"મંજૂરી આપો"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ના, આભાર"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"વાઇ-ફાઇ આપમેળે ચાલુ થઈ જશે"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"જ્યારે તમે એક ઉચ્ચ ક્વૉલિટીવાળા સાચવેલ નેટવર્કની નજીક હોવ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"પાછું ચાલુ કરશો નહીં"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"અવર્ગીકૃત"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"તમે આ સૂચનાઓનું મહત્વ સેટ કર્યું છે."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"શામેલ થયેલ લોકોને કારણે આ મહત્વપૂર્ણ છે."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ને <xliff:g id="ACCOUNT">%2$s</xliff:g> સાથે એક નવા વપરાશકર્તાને બનાવવાની મંજૂરી આપીએ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> સાથે <xliff:g id="APP">%1$s</xliff:g> ને એક નવા વપરાશકર્તાને બનાવવાની મંજૂરી આપીએ (આ એકાઉન્ટ સાથેના એક વપરાશકર્તા પહેલાંથી અસ્તિત્વમાં છે)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ભાષા ઉમેરો"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"પ્રદેશ પસંદગી"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ભાષાનું નામ ટાઇપ કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 32bcd4c..f21d138 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1155,7 +1155,7 @@
     <string name="whichImageCaptureApplicationNamed" msgid="8619384150737825003">"%1$s के साथ चित्र कैप्चर करें"</string>
     <string name="whichImageCaptureApplicationLabel" msgid="6390303445371527066">"चित्र कैप्चर करें"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"इस कार्रवाई के लिए डिफ़ॉल्‍ट के तौर पर इस्तेमाल करें"</string>
-    <string name="use_a_different_app" msgid="8134926230585710243">"किसी भिन्न ऐप्स का उपयोग करें"</string>
+    <string name="use_a_different_app" msgid="8134926230585710243">"किसी दूसरे ऐप्लिकेशन का इस्तेमाल करें"</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"सिस्‍टम सेटिंग और डाउनलोड किए गए ऐप में डिफ़ॉल्‍ट साफ़ करें."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"कोई कार्रवाई चुनें"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"USB डिवाइस के लिए कोई ऐप्स  चुनें"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सभी नेटवर्क देखने के लिए यहां पर टैप करें"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करें"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सभी नेटवर्क"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सुझाए गए वाई-फ़ाई नेटवर्क को अनुमति देना चाहते हैं?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> के सुझाए गए नेटवर्क. डिवाइस अपने आप कनेक्ट हो सकता है."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमति दें"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"रहने दें"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाई-फ़ाई अपने आप चालू हो जाएगा"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"जब आप किसी अच्छी क्वालिटी वाले सेव किए गए नेटवर्क के पास हों"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"वापस चालू न करें"</string>
@@ -1351,7 +1347,7 @@
     <string name="dlg_ok" msgid="7376953167039865701">"ठीक है"</string>
     <string name="usb_charging_notification_title" msgid="1595122345358177163">"यह डिवाइस यूएसबी से चार्ज हो रहा है"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"जोड़ा गया डिवाइस यूएसबी के ज़रिए चार्ज हो रहा है"</string>
-    <string name="usb_mtp_notification_title" msgid="4238227258391151029">"यूएसबी फ़ाइल ट्रांसफ़र की सुविधा चालू की गई"</string>
+    <string name="usb_mtp_notification_title" msgid="4238227258391151029">"यूएसबी फ़ाइल ट्रांसफ़र करने की सुविधा चालू की गई"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"यूएसबी के ज़रिए पीटीपी की सुविधा चालू की गई"</string>
     <string name="usb_tether_notification_title" msgid="3716143122035802501">"यूएसबी टेदरिंग की सुविधा चालू की गई"</string>
     <string name="usb_midi_notification_title" msgid="5356040379749154805">"यूएसबी के ज़रिए एमआईडीआई (मिडी) की सुविधा चालू की गई"</string>
@@ -1360,7 +1356,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"जोड़ा गया डिवाइस चार्ज हो रहा है. ज़्यादा विकल्पों के लिए टैप करें."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"एनालॉग ऑडियो एक्सेसरी का पता चला"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"अटैच किया गया डिवाइस इस फ़ोन से संगत नहीं है. ज़्यादा जानने के लिए टैप करें."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"डीबग करने के लिए एडीबी कनेक्ट किया गया"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"यूएसबी डीबग करने के लिए एडीबी कनेक्ट किया गया"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"यूएसबी को डीबग करने की सुविधा बंद करने के लिए टैप करें"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB डीबग करना अक्षम करने के लिए चुनें."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"टेस्ट हार्नेस मोड चालू किया गया"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"बिना किसी श्रेणी के"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"आपने इन सूचनाओं की अहमियत सेट की है."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"यह मौजूद व्यक्तियों के कारण महत्वपूर्ण है."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> को <xliff:g id="ACCOUNT">%2$s</xliff:g> के ज़रिये एक नया उपयोगकर्ता बनाने दें?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> को <xliff:g id="ACCOUNT">%2$s</xliff:g> के ज़रिये एक नया उपयोगकर्ता बनाने दें (इस खाते वाले एक उपयोगकर्ता पहले से मौजूद हैं)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"भाषा जोड़ें"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"क्षेत्र प्राथमिकता"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषा का नाम लिखें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 13d8433..25007d5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite za prikaz svih mreža"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite li dopustiti predložene Wi‑Fi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže aplikacija <xliff:g id="NAME">%s</xliff:g>. Uređaji se mogu povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dopusti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se uključiti automatski"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini spremljene mreže visoke kvalitete"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Više ne uključuj"</string>
@@ -1700,7 +1696,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Prečac pristupačnosti isključio je uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Pritisnite i zadržite tipke za glasnoću na tri sekunde da biste koristili uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Odaberite uslugu koju ćete upotrebljavati kad dodirnete gumb pristupačnosti:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Odaberite uslugu koju ćete upotrebljavati uz pokret pristupačnosti (prelazak s dva prsta prema gore od dna zaslona):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Odaberite za što će se upotrebljavati pokret pristupačnosti (prelazak s dva prsta prema gore od dna zaslona):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Odaberite uslugu koju ćete upotrebljavati uz pokret pristupačnosti (prelazak s tri prsta prema gore od dna zaslona):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Da biste prešli na neku drugu uslugu, dodirnite i zadržite gumb pristupačnosti."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Da biste prešli na neku drugu uslugu, prijeđite s dva prsta prema gore i zadržite."</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nema kategorije"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Postavili ste važnost tih obavijesti."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Važno je zbog uključenih osoba."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Želite li dopustiti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da izradi novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Želite li dopustiti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da izradi novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g> (korisnik s tim računom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodavanje jezika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Postavke regije"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index a08a180..0487601a 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Koppintással megjelenítheti az összes hálózatot"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kapcsolódás"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Összes hálózat"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Engedélyezi a javasolt Wi-Fi-hálózatokat?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"A(z) <xliff:g id="NAME">%s</xliff:g> hálózatokat javasolt. Az eszköz automatikusan csatlakozhat hozzájuk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Engedélyezés"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nem, köszönöm"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A Wi-Fi automatikusan bekapcsol"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Amikor jó minőségű mentett hálózat közelében tartózkodik"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne kapcsolódjon vissza"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nincs kategóriába sorolva"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ön állította be ezen értesítések fontossági szintjét."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ez az üzenet a résztvevők miatt fontos."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> számára, hogy új felhasználót hozzon létre a(z) <xliff:g id="ACCOUNT">%2$s</xliff:g> fiókkal?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> számára, hogy új felhasználót hozzon létre a(z) <xliff:g id="ACCOUNT">%2$s</xliff:g> fiókkal? (Már létezik felhasználó ezzel a fiókkal.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Nyelv hozzáadása"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Régió beállítása"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Adja meg a nyelvet"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 6b02ea1..e6223f0 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Հպեք՝ բոլոր ցանցերը տեսնելու համար"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Միանալ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Բոլոր ցանցերը"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Թույլատրե՞լ առաջարկվող Wi‑Fi ցանցերի օգտագործումը"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> հավելվածի առաջարկվող ցանցեր: Սարքը կարող է ավտոմատ միանալ:"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Թույլատրել"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ոչ, շնորհակալություն"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi-ն ավտոմատ կմիանա"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Լավ ազդանշանով պահված ցանցի տարածքում գտնվելիս"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Նորից չմիացնել"</string>
@@ -1360,9 +1356,9 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Միացված սարքի լիցքավորում: Հպեք՝ ավելի շատ ընտրանքների համար:"</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Հայտնաբերված է անալոգային աուդիո լրասարք"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Կցված սարքը համատեղելի չէ այս հեռախոսի հետ: Հպեք` ավելին իմանալու համար:"</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB վրիպազերծումը միացված է"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Հպեք՝ USB վրիպազերծումն անջատելու համար"</string>
-    <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Ընտրել` USB կարգաբերումը կասեցնելու համար:"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-ով վրիպազերծումը միացված է"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Հպեք՝ USB-ով վրիպազերծումն անջատելու համար"</string>
+    <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Սեղմեք՝ USB-ով վրիպազերծումն անջատելու համար:"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Թեստային ռեժիմը միացված է"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Թեստային ռեժիմն անջատելու համար զրոյացրեք կարգավորումները։"</string>
     <string name="usb_contaminant_detected_title" msgid="7136400633704058349">"USB միացքում ջուր կամ աղտ է հայտնաբերվել"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Չդասակարգված"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Դուք սահմանել եք այս ծանուցումների կարևորությունը:"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Կարևոր է, քանի որ որոշակի մարդիկ են ներգրավված:"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Թույլատրե՞լ <xliff:g id="APP">%1$s</xliff:g> հավելվածին <xliff:g id="ACCOUNT">%2$s</xliff:g> հաշվով նոր Օգտատեր ստեղծել:"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Թույլատրե՞լ <xliff:g id="APP">%1$s</xliff:g> հավելվածին <xliff:g id="ACCOUNT">%2$s</xliff:g> հաշվով նոր Օգտատեր ստեղծել (նման հաշվով Օգտատեր արդեն գոյություն ունի):"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ավելացնել լեզու"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Նախընտրելի տարածաշրջան"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Մուտքագրեք լեզուն"</string>
@@ -1934,7 +1932,7 @@
     <string name="app_category_maps" msgid="5878491404538024367">"Քարտեզներ և նավարկում"</string>
     <string name="app_category_productivity" msgid="3742083261781538852">"Արդյունավետություն"</string>
     <string name="device_storage_monitor_notification_channel" msgid="3295871267414816228">"Սարքի հիշողություն"</string>
-    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB վրիպազերծում"</string>
+    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB-ով վրիպազերծում"</string>
     <string name="time_picker_hour_label" msgid="2979075098868106450">"ժամ"</string>
     <string name="time_picker_minute_label" msgid="5168864173796598399">"րոպե"</string>
     <string name="time_picker_header_text" msgid="143536825321922567">"Ժամը"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index beda1c2..3c6ee83 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ketuk untuk melihat semua jaringan"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Hubungkan"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua jaringan"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Izinkan jaringan Wi-Fi yang disarankan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Jaringan yang disarankan <xliff:g id="NAME">%s</xliff:g>. Perangkat dapat terhubung secara otomatis."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Izinkan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Lain kali"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan aktif otomatis"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saat berada di dekat jaringan berkualitas tinggi yang tersimpan"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan aktifkan kembali"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Belum dikategorikan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Anda menyetel nilai penting notifikasi ini."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ini penting karena orang-orang yang terlibat."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Izinkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baru dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Izinkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baru dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> (Pengguna dengan akun ini sudah ada) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tambahkan bahasa"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferensi wilayah"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ketik nama bahasa"</string>
@@ -1983,7 +1981,7 @@
     <string name="popup_window_default_title" msgid="4874318849712115433">"Jendela Pop-up"</string>
     <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="shortcut_restored_on_lower_version" msgid="4860853725206702336">"Versi aplikasi di-downgrade, atau tidak kompatibel dengan pintasan ini"</string>
-    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan karena aplikasi tidak mendukung backup dan pulihkan"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan karena aplikasi tidak mendukung pencadangan dan pemulihan"</string>
     <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Tidak dapat memulihkan pintasan karena tanda tangan aplikasi tidak cocok"</string>
     <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Tidak dapat memulihkan pintasan."</string>
     <string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"Pintasan dinonaktifkan"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f4be925..59cd4c1 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ýttu til að sjá öll netkerfi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Tengjast"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Öll netkerfi"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Leyfa ráðlögð Wi‑Fi net?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> kom með tillögur að netkerfum. Tækið gæti tengst sjálfkrafa."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Leyfa"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nei, takk"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kveikt verður sjálfkrafa á Wi‑Fi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Þegar þú ert nálægt vistuðu hágæðaneti"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ekki kveikja aftur"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Óflokkað"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Þú stilltir mikilvægi þessara tilkynninga."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Þetta er mikilvægt vegna fólksins sem tekur þátt í þessu."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að stofna nýjan notanda með <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að stofna nýjan notanda með <xliff:g id="ACCOUNT">%2$s</xliff:g> (notandi með þennan reikning er þegar fyrir hendi)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Bæta við tungumáli"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Svæðisval"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sláðu inn heiti tungumáls"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 1c3d684..cbc9989 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tocca per vedere tutte le reti"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connetti"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tutte le reti"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vuoi consentire le reti Wi-Fi suggerite?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ha suggerito delle reti. Il dispositivo potrebbe collegarsi automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Consenti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, grazie"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Il Wi‑Fi verrà attivato automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando ti trovi nell\'area di una rete salvata di alta qualità"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non riattivare"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Senza categoria"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Stabilisci tu l\'importanza di queste notifiche."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Importante a causa delle persone coinvolte."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Consentire a <xliff:g id="APP">%1$s</xliff:g> di creare un nuovo utente con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Consentire a <xliff:g id="APP">%1$s</xliff:g> di creare un nuovo utente con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Esiste già un utente con questo account)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Aggiungi una lingua"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Area geografica preferita"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digita nome lingua"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 8ca1d23..0a721a8 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -102,7 +102,7 @@
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏העמית ביקש TTY במצב HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏העמית ביקש TTY במצב VCO"</string>
     <string name="peerTtyModeOff" msgid="3280819717850602205">"‏העמית ביקש TTY במצב OFF"</string>
-    <string name="serviceClassVoice" msgid="1258393812335258019">"Google Voice"</string>
+    <string name="serviceClassVoice" msgid="1258393812335258019">"קול"</string>
     <string name="serviceClassData" msgid="872456782077937893">"Google Data"</string>
     <string name="serviceClassFAX" msgid="5566624998840486475">"פקס"</string>
     <string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
@@ -440,10 +440,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"האפליקציה מזהה את הפעילות הגופנית שלך."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"צלם תמונות וסרטונים"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"אפליקציה זו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים בכל עת."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"הרשאת גישה לאפליקציה או לשירות למצלמות המערכת כדי לצלם תמונות וסרטונים"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"‏לאפליקציית המערכת | הזו יש הרשאות מיוחדות והיא יכולה לצלם תמונות ולהקליט סרטונים באמצעות מצלמת מערכת בכל זמן. בנוסף, לאפליקציה נדרשת ההרשאה android.permission.CAMERA"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"שליטה ברטט"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"מאפשר לאפליקציה לשלוט ברטט."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"התקשר ישירות למספרי טלפון"</string>
@@ -1303,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"יש להקיש כדי לראות את כל הרשתות"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"התחבר"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"כל הרשתות"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏לאפשר הצעות לרשתות Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"הצעות לרשתות <xliff:g id="NAME">%s</xliff:g>. ייתכן שחיבור המכשיר ייעשה באופן אוטומטי."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"אישור"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"לא תודה"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏ה-Wi-Fi יופעל אוטומטית"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"כשתימצאו בקרבת רשת באיכות גבוהה ששמרתם"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"אל תפעיל שוב"</string>
@@ -1964,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ללא שיוך לקטגוריה"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"עליך להגדיר את החשיבות של ההתראות האלה."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ההודעה חשובה בשל האנשים המעורבים."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"האם לאפשר ל-<xliff:g id="APP">%1$s</xliff:g> ליצור משתמש חדש לחשבון <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"האם לאפשר ל-<xliff:g id="APP">%1$s</xliff:g> ליצור משתמש חדש לחשבון <xliff:g id="ACCOUNT">%2$s</xliff:g> (כבר קיים משתמש לחשבון הזה) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"הוספת שפה"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"העדפת אזור"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"הקלד שם שפה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 861518c..fe45c1f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"すべてのネットワークを表示するにはタップします"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"接続"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"すべてのネットワーク"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Wi‑Fi ネットワーク候補を許可しますか?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> からのネットワーク候補に、デバイスが自動的に接続される可能性があります。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"許可"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"許可しない"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi は自動的にオンになります"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"高品質の保存済みネットワークの検出時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"再度オンにしない"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"カテゴリなし"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"このような通知の重要度を設定します。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"関係するユーザーのため、この設定は重要です。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> が <xliff:g id="ACCOUNT">%2$s</xliff:g> で新しいユーザーを作成できるようにしますか?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> が <xliff:g id="ACCOUNT">%2$s</xliff:g> で新しいユーザーを作成できるようにしますか?(このアカウントのユーザーはすでに存在します)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"言語を追加"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地域設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"言語名を入力"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 1dba3ca..00afe026 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"შეეხეთ ყველა ქსელის სანახავად"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"დაკავშირება"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ყველა ქსელი"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"გსურთ, დაუშვათ შემოთავაზებული Wi‑Fi ქსელები?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> შემოთავაზებული ქსელები. მოწყობილობა შეიძლება ავტომატურად დაუკავშირდეს."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"დაშვება"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"არა, გმადლობთ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi ავტომატურად ჩაირთვება"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"როცა შენახულ მაღალხარისხიან ქსელებთან ახლოს იმყოფებით"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ხელახლა ნუ ჩართავ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"კატეგორიის გარეშე"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ამ შეტყობინებების მნიშვნელობის დონე განისაზღვრა თქვენ მიერ."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"მნიშვნელოვანია ჩართული მომხმარებლების გამო."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"მიეცეს უფლება <xliff:g id="APP">%1$s</xliff:g>-ს, <xliff:g id="ACCOUNT">%2$s</xliff:g>-ის მეშვეობით ახალი მომხმარებელი შექმნას ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"მიეცეს უფლება <xliff:g id="APP">%1$s</xliff:g>-ს, <xliff:g id="ACCOUNT">%2$s</xliff:g>-ის მეშვეობით ახალი მომხმარებელი შექმნას (ამ ანგარიშის მქონე მომხმარებელი უკვე არსებობს) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ენის დამატება"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"რეგიონის პარამეტრები"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"აკრიფეთ ენის სახელი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 504ce74..81f512b 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Барлық желілерді көру үшін түртіңіз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Қосылу"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Барлық желілер"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Ұсынылған Wi‑Fi желілеріне рұқсат беру керек пе?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ұсынған желілер. Құрылғы автоматты түрде қосылуы мүмкін."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Рұқсат беру"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Жоқ, рақмет"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматты түрде қосылады"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Сақталған жоғары сапалы желіге жақын болғанда"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Қайта қоспау"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Санатқа жатқызылмаған"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Сіз осы хабарландырулардың маңыздылығын орнатасыз."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Қатысты адамдарға байланысты бұл маңызды."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACCOUNT">%2$s</xliff:g> есептік жазбасы бар жаңа пайдаланушы жасауға рұқсат ету керек пе?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACCOUNT">%2$s</xliff:g> есептік жазбасында жаңа пайдаланушы жасауға рұқсат ету керек пе (осы есептік жазбасы бар пайдаланушы әлдеқашан бар) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Тілді қосу"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Аймақ параметрі"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Тіл атауын теріңіз"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 33aa8b2..539747b 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1259,14 +1259,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ចុចដើម្បីមើលបណ្តាញទាំងអស់"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ភ្ជាប់"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"បណ្ដាញ​ទាំងអស់"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"អនុញ្ញាតឱ្យ​ភ្ជាប់​បណ្ដាញ Wi‑Fi ដែលបាន​ណែនាំ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"បណ្ដាញ​ដែលបាន​ណែនាំ​របស់ <xliff:g id="NAME">%s</xliff:g> ។ ឧបករណ៍​អាច​ភ្ជាប់​ដោយស្វ័យប្រវត្តិ។"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"អនុញ្ញាត"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ទេ អរគុណ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi នឹង​បើក​ដោយ​ស្វ័យប្រវត្តិ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"នៅពេល​ដែល​អ្នក​នៅ​ជិត​បណ្តាញ​គុណភាព​ខ្ពស់​ដែល​បាន​រក្សាទុក"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"កុំ​បើក​ឡើង​វិញ"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"មិន​​បែងចែក​ប្រភេទ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"អ្នកបានកំណត់សារៈសំខាន់នៃការជូនដំណឹងទាំងនេះ"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"វាមានសារៈសំខាន់ដោយសារតែមនុស្សដែលពាក់ព័ន្ធ"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"អនុញ្ញាតឲ្យ <xliff:g id="APP">%1$s</xliff:g> បង្កើតអ្នកប្រើថ្មីដោយប្រើ <xliff:g id="ACCOUNT">%2$s</xliff:g> ឬទេ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"អនុញ្ញាតឲ្យ <xliff:g id="APP">%1$s</xliff:g> បង្កើតអ្នកប្រើថ្មីដោយប្រើ <xliff:g id="ACCOUNT">%2$s</xliff:g> (មានអ្នកប្រើសម្រាប់គណនីនេះរួចហើយ) ឬទេ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"បន្ថែមភាសា"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ចំណូលចិត្តតំបន់"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"វាយបញ្ចូលឈ្មោះភាសា"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 654dd6b..c7d0fea 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1155,7 +1155,7 @@
     <string name="whichImageCaptureApplicationNamed" msgid="8619384150737825003">"%1$s ಜೊತೆ ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
     <string name="whichImageCaptureApplicationLabel" msgid="6390303445371527066">"ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"ಈ ಕ್ರಿಯೆಗೆ ಡಿಫಾಲ್ಟ್ ಆಗಿ ಬಳಸಿ."</string>
-    <string name="use_a_different_app" msgid="8134926230585710243">"ಬೇರೆಯ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ"</string>
+    <string name="use_a_different_app" msgid="8134926230585710243">"ಬೇರೊಂದು ಆ್ಯಪ್ ಬಳಸಿ"</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"ಸಿಸ್ಟಂ ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಅಪ್ಲಿಕೇಶನ್‌ಗಳು &gt; ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾದ ಡಿಫಾಲ್ಟ್‌‌ ಅನ್ನು ತೆರವುಗೊಳಿಸಿ."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"ಕ್ರಿಯೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"USB ಸಾಧನಕ್ಕೆ ಅಪ್ಲಿಕೇಶನ್‌‌ವೊಂದನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ಸಂಪರ್ಕಿಸಿ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳು"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ಸೂಚಿಸಿರುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ಸೂಚಿಸಿರುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳು. ಸಾಧನಗಳು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಗೊಳ್ಳಬಹುದು."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ಅನುಮತಿಸಿ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ಬೇಡ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ವೈ‑ಫೈ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಆಗುತ್ತದೆ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ನೀವು ಉಳಿಸಿದ ಅಧಿಕ ಗುಣಮಟ್ಟದ ನೆಟ್‌ವರ್ಕ್‌ ಸಮೀಪದಲ್ಲಿದ್ದಾಗ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ಮತ್ತೆ ಆನ್ ಮಾಡಲು ಹಿಂತಿರುಗಬೇಡಿ"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ವರ್ಗೀಕರಿಸದಿರುವುದು"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ನೀವು ಈ ಅಧಿಸೂಚನೆಗಳ ಪ್ರಾಮುಖ್ಯತೆಯನ್ನು ಹೊಂದಿಸಿರುವಿರಿ."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ಜನರು ತೊಡಗಿಕೊಂಡಿರುವ ಕಾರಣ ಇದು ಅತ್ಯಂತ ಪ್ರಮುಖವಾಗಿದೆ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ಮೂಲಕ ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> (ಈ ಖಾತೆಯ ಬಳಕೆದಾರರು ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದ್ದಾರೆ) ಮೂಲಕ ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ಭಾಷೆ ಸೇರಿಸಿ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ಪ್ರದೇಶ ಪ್ರಾಶಸ್ತ್ಯ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ಭಾಷೆ ಹೆಸರನ್ನು ಟೈಪ್ ಮಾಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index bc6193f..c0dd8b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"모든 네트워크를 보려면 탭하세요."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"연결"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"모든 네트워크"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"제안된 Wi‑Fi 네트워크를 허용하시겠습니까?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>에서 네트워크를 제안했습니다. 기기가 자동으로 연결될 수 있습니다."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"허용"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"허용 안함"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi가 자동으로 사용 설정됨"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"저장된 고품질 네트워크가 가까이 있는 경우"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"다시 사용 설정하지 않음"</string>
@@ -1349,7 +1345,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"권한 필요 없음"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"비용이 부과될 수 있습니다."</string>
     <string name="dlg_ok" msgid="7376953167039865701">"확인"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"이 기기를 USB로 충전"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"이 기기를 USB로 충전 중"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"USB를 통해 연결된 기기 충전"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"USB 파일 전송 사용 설정됨"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"USB를 통해 PTP 사용 설정됨"</string>
@@ -1861,8 +1857,8 @@
     <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"다운타임"</string>
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"평일 밤"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"주말"</string>
-    <string name="zen_mode_default_events_name" msgid="8158334939013085363">"캘린더 일정 중"</string>
-    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"수면 중"</string>
+    <string name="zen_mode_default_events_name" msgid="8158334939013085363">"캘린더 일정"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"수면 시간"</string>
     <string name="muted_by" msgid="5942954724562097128">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"사용 중인 기기 내부에 문제가 발생했습니다. 자세한 내용은 제조업체에 문의하세요."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"지정된 카테고리 없음"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"이러한 알림의 중요도를 설정했습니다."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"관련된 사용자가 있으므로 중요합니다."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g>이(가) <xliff:g id="ACCOUNT">%2$s</xliff:g>(으)로 신규 사용자를 만들도록 허용하시겠습니까?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>이(가) <xliff:g id="ACCOUNT">%2$s</xliff:g>(이 계정의 사용자가 이미 있음)(으)로 신규 사용자를 만들도록 허용하시겠습니까?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"언어 추가"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"지역 환경설정"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"언어 이름 입력"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 75eb9ac..9182563 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бардык тармактарды көрүү үчүн басыңыз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Туташуу"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Бардык тармактар"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Сунушталган Wi‑Fi тармактарына туташасызбы?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> сунуштаган тармактар. Түзмөк автоматтык түрдө туташышы мүмкүн."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Уруксат берүү"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Жок, рахмат"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматтык түрдө күйөт"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Байланыш сигналы күчтүү тармактарга жакындаганда"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Өзү кайра күйбөйт"</string>
@@ -1350,7 +1346,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"Эч уруксаттын кереги жок"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"бул үчүн акы алынышы мүмкүн"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"Жарайт"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Бул түзмөк USB аркылуу кубатталууда"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"USB аркылуу кубатталууда"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"USB аркылуу туташкан түзмөк кубатталууда"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"USB аркылуу файл өткөрүү режими күйгүзүлдү"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"USB аркылуу PTP режими күйгүзүлдү"</string>
@@ -1362,7 +1358,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Аналогдук аудио жабдуу табылды"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Тиркелген түзмөк бул телефонго шайкеш келбейт. Көбүрөөк маалымат алуу үчүн таптап коюңуз."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Мүчүлүштүктөрдү USB аркылуу оңдоо иштетилген"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"USB аркылуу мүчүлүштүктөрдү оңдоону өчүрүү үчүн таптаңыз"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"USB арклуу мүчүлштктрдү оңдоону өчрүү үчүн басып коюңуз"</string>
     <!-- no translation found for adb_active_notification_message (8470296818270110396) -->
     <skip />
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Сыноо программасынын режими иштетилди"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Категорияларга бөлүнгөн эмес"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Бул эскертмелердин маанилүүлүгүн белгиледиңиз."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Булар сиз үчүн маанилүү адамдар."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> колдонмосу <xliff:g id="ACCOUNT">%2$s</xliff:g> аккаунту менен жаңы колдонуучу түзө берсинби?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> колдонмосуна <xliff:g id="ACCOUNT">%2$s</xliff:g> аккаунту үчүн жаңы колдонуучу түзгөнгө уруксат бересизби (мындай аккаунту бар колдонуучу мурунтан эле бар)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Тил кошуу"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Чөлкөмдүк жөндөөлөр"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Тилди киргизиңиз"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index f20b90a..0189c08 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ແຕະເພື່ອເບິ່ງເຄືອຂ່າຍທັງໝົດ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ເຊື່ອມ​ຕໍ່"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ເຄືອຂ່າຍທັງໝົດ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ອະນຸຍາດເຄືອຂ່າຍ Wi‑Fi ທີ່ແນະນຳບໍ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"ເຄືອຂ່າຍ <xliff:g id="NAME">%s</xliff:g> ທີ່ແນະນຳ. ອຸປະກອນອາດເຊື່ອມຕໍ່ເອງໂດຍອັດຕະໂນມັດ."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ອະນຸຍາດ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ບໍ່, ຂອບໃຈ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ຈະມີການເປີດໃຊ້ Wi‑Fi ອັດຕະໂນມັດ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ເມື່ອທ່ານຢູ່ໃກ້ເຄືອຂ່າຍຄຸນນະພາບສູງທີ່ບັນທຶກໄວ້"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ບໍ່ຕ້ອງເປີດໃຊ້ຄືນໃໝ່"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ບໍ່​ມີ​ໝວດ​ໝູ່"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ທ່ານຕັ້ງຄວາມສຳຄັນຂອງການແຈ້ງເຕືອນເຫຼົ່ານີ້."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ຂໍ້ຄວາມນີ້ສຳຄັນເນື່ອງຈາກບຸກຄົນທີ່ກ່ຽວຂ້ອງ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"ອະນຸຍາດໃຫ້ <xliff:g id="APP">%1$s</xliff:g> ສ້າງຜູ້ໃຊ້ໃໝ່ສຳລັບ <xliff:g id="ACCOUNT">%2$s</xliff:g> ບໍ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"ອະນຸຍາດໃຫ້ <xliff:g id="APP">%1$s</xliff:g> ສ້າງຜູ້ໃຊ້ໃໝ່ສຳລັບ <xliff:g id="ACCOUNT">%2$s</xliff:g> (ຜູ້ໃຊ້ສຳລັບບັນຊີນີ້ມີຢູ່ແລ້ວ) ບໍ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ເພີ່ມພາສາ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ການຕັ້ງຄ່າພາກພື້ນ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ພິມຊື່ພາສາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 1256e3e..0d29c84 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Palieskite, jei norite matyti visus tinklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Prisijungti"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Visi tinklai"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Leisti siūlomus „Wi‑Fi“ tinklus?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"„<xliff:g id="NAME">%s</xliff:g>“ siūlomi tinklai. Įrenginys gali prisijungti automatiškai."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Leisti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, ačiū"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"„Wi‑Fi“ bus įjungtas automatiškai"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kai būsite netoli išsaugoto aukštos kokybės tinklo"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neįjunkite vėl"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Be kategorijos"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Galite nustatyti šių pranešimų svarbą."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tai svarbu dėl susijusių žmonių."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ kurti naują <xliff:g id="ACCOUNT">%2$s</xliff:g> naudotoją?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ kurti naują <xliff:g id="ACCOUNT">%2$s</xliff:g> naudotoją (šią paskyrą naudojantis naudotojas jau yra)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pridėkite kalbą"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiono nuostata"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Įveskite kalbos pav."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 5ff3aaf..2547ba9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Pieskarieties, lai skatītu visus tīklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Izveidot savienojumu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Visi tīkli"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vai atļaut ieteiktos Wi‑Fi tīklus?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Lietotnes <xliff:g id="NAME">%s</xliff:g> ieteiktie tīkli. Ierīcē var tikt automātiski izveidots savienojums."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Atļaut"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nē, paldies"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi tiks automātiski ieslēgts"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kad atrodaties saglabāta augstas kvalitātes tīkla tuvumā"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neieslēgt atkārtoti"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nav kategorijas"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Jūs iestatījāt šo paziņojumu svarīguma līmeni."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tas ir svarīgi iesaistīto personu dēļ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vai atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> izveidot jaunu lietotāju, izmantojot e-pasta adresi <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vai atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> izveidot jaunu lietotāju, izmantojot e-pasta adresi <xliff:g id="ACCOUNT">%2$s</xliff:g> (lietotājs ar šādu kontu jau pastāv)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pievienot valodu"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Reģiona preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ierakstiet valodas nosaukumu"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index ef027a9..9949eaa 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -243,7 +243,7 @@
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Тивок режим"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Звукот е исклучен"</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Звукот е вклучен"</string>
-    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Режим на работа во авион"</string>
+    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Авионски режим"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Режимот на работа во авион е вклучен"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Режимот на работа во авион е исклучен"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Поставки"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Допрете за да ги видите сите мрежи"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Поврзете се"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Сите мрежи"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Да се дозволат предложените Wi‑Fi мрежи?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Предложени мрежи од <xliff:g id="NAME">%s</xliff:g>. Уредот може да се поврзе автоматски."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволи"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, фала"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ќе се вклучи автоматски"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Кога сте во близина на зачувана мрежа со висок квалитет"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вклучувај повторно"</string>
@@ -1362,7 +1358,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Откриен е аналоген аудиододаток"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Приложениот уред не е компатибилен со телефонов. Допрете за да дознаете повеќе."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Поврзано е отстранување грешки преку USB"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Допрете за да го исклучите отстранувањето грешки преку USB"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Допрете за да го исклучите"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Изберете за да се оневозможи отстранување грешки на USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Овозможен е режимот на рамка за тестирање"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Извршете фабричко ресетирање за да го оневозможите режимот на рамка за тестирање."</string>
@@ -1897,8 +1893,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризирано"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ја поставивте важноста на известувањава."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ова е важно заради луѓето кои се вклучени."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дозволувате ли <xliff:g id="APP">%1$s</xliff:g> да создаде нов корисник со <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дозволувате ли <xliff:g id="APP">%1$s</xliff:g> да создаде нов корисник со <xliff:g id="ACCOUNT">%2$s</xliff:g> (веќе постои корисник со оваа сметка)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додај јазик"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Претпочитувања за регион"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Внеси име на јазик"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index f77c223..f711158 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"നിങ്ങളുടെ ശാരീരിക പ്രവർത്തനം ഈ ആപ്പിന് തിരിച്ചറിയാനാവും."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കുക"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"ഏതുസമയത്തും ക്യാമറ ഉപയോഗിച്ചുകൊണ്ട് ചിത്രങ്ങൾ എടുക്കാനും വീഡിയോകൾ റെക്കോർഡുചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കാൻ, സിസ്‌റ്റം ക്യാമറ ആക്‌സസ് ചെയ്യുന്നതിന് ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"സിസ്‌റ്റം ക്യാമറ ഉപയോഗിച്ച് ഏത് സമയത്തും ചിത്രങ്ങളെടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ വിശേഷാധികാര | സിസ്‌റ്റം ആപ്പിന് കഴിയും. ആപ്പിലും android.permission.CAMERA അനുമതി ഉണ്ടായിരിക്കണം"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"വൈബ്രേറ്റുചെയ്യൽ നിയന്ത്രിക്കുക"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"വൈബ്രേറ്റർ നിയന്ത്രിക്കുന്നതിന് അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ഫോൺ നമ്പറുകളിലേക്ക് നേരിട്ട് വിളിക്കുക"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"എല്ലാ നെറ്റ്‌വർക്കുകളും കാണാൻ ടാപ്പുചെയ്യുക"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"കണക്റ്റുചെയ്യുക"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"എല്ലാ നെറ്റ്‌വർക്കുകളും"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"നിർദ്ദേശിച്ച വെെഫെെ നെറ്റ്‌വർക്കുകൾ അനുവദിക്കണോ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> നിർദ്ദേശിച്ച നെറ്റ്‌വർക്കുകൾ. ഉപകരണം സ്വയമേവ കണക്‌റ്റ് ചെയ്‌തേക്കാം."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"അനുവദിക്കുക"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"വേണ്ട"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"വൈഫൈ സ്വമേധയാ ഓണാകും"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"നിങ്ങൾ ഉയർന്ന നിലവാരമുള്ള സംരക്ഷിക്കപ്പെട്ട നെറ്റ്‌വർക്കിനരികിലെത്തുമ്പോൾ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"തിരികെ ഓണാക്കരുത്"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"വർഗ്ഗീകരിച്ചിട്ടില്ലാത്ത"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ഈ അറിയിപ്പുകളുടെ പ്രാധാന്യം നിങ്ങൾ സജ്ജീകരിച്ചു."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ഉൾപ്പെട്ടിട്ടുള്ള ആളുകളെ കണക്കിലെടുക്കുമ്പോള്‍ ഇത് പ്രധാനപ്പെട്ടതാണ്‌."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> എന്ന അക്കൗണ്ട് ഉപയോഗിച്ച് പുതിയൊരു ഉപയോക്താവിനെ സൃഷ്ടിക്കാൻ <xliff:g id="APP">%1$s</xliff:g> എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> എന്ന അക്കൗണ്ട് (ഈ അക്കൗണ്ട് ഉപയോഗിച്ചുള്ള ഒരു ഉപയോക്താവ് ഇതിനകം തന്നെ നിലവിലുണ്ട്) ഉപയോഗിച്ച് പുതിയൊരു ഉപയോക്താവിനെ സൃഷ്ടിക്കാൻ <xliff:g id="APP">%1$s</xliff:g> എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ഒരു ഭാഷ ചേർക്കുക"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"മേഖലാ മുൻഗണന"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ഭാഷയുടെ പേര് ടൈപ്പുചെയ്യുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index c530d3e..7570028 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бүх сүлжээг харахын тулд товшино уу"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Холбогдох"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Бүх сүлжээ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Санал болгосон Wi‑Fi сүлжээг зөвшөөрөх үү?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> сүлжээ санал болголоо. Төхөөрөмж автоматаар холбогдож магадгүй."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Зөвшөөрөх"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Үгүй, баярлалаа"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматаар асна"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Таныг хадгалсан, өндөр чанартай сүлжээний ойролцоо байх үед"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Буцааж асаахгүй"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ангилаагүй"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Та эдгээр мэдэгдлийн ач холбогдлыг тогтоосон."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Оролцсон хүмүүсээс шалтгаалан энэ нь өндөр ач холбогдолтой."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g>-г <xliff:g id="ACCOUNT">%2$s</xliff:g>-р шинэ Хэрэглэгч үүсгэхийг зөвшөөрөх үү?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>-г <xliff:g id="ACCOUNT">%2$s</xliff:g>-р шинэ хэрэглэгч үүсгэхийг зөвшөөрөх үү (ийм бүртгэлтэй хэрэглэгч аль хэдийн байна) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Хэл нэмэх"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Бүс нутгийн тохиргоо"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Улсын хэлийг бичнэ үү"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 929fedd..e22bca0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"हे अ‍ॅप तुमच्या शारीरिक अॅक्टिव्हिटी ओळखू शकते."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"चित्रे आणि व्हिडिओ घ्या"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"हा अ‍ॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्ह‍िडिअो रेकॉर्ड करू शकतो."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"फोटो आणि व्हिडिओ काढण्यासाठी अॅप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे अॅक्सेस करण्याची अनुमती द्या"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"हे विशेषाधिकृत आहे | सिस्टम अॅप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. अॅपला android.permission.CAMERA परवानगी देण्याचीदेखील आवश्यकता आहे"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"व्हायब्रेट नियंत्रित करा"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"अ‍ॅप ला व्हायब्रेटर नियंत्रित करण्यासाठी अनुमती देते."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"फोन नंबरवर प्रत्यक्ष कॉल करा"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सर्व नेटवर्क पाहण्यासाठी टॅप करा"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करा"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सर्व नेटवर्क"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सुचवलेल्या वाय-फाय नेटवर्कना अनुमती द्यायची का?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> सुचवलेली नेटवर्क. डिव्हाइस आपोआप कनेक्ट होऊ शकते."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमती द्या"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"नाही, नको"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाय-फाय आपोआप चालू होईल"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तुम्ही जेव्हा सेव्ह केलेल्या उच्च दर्जाच्या नेटवर्कजवळ असाल तेव्हा"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"पुन्हा चालू करू नका"</string>
@@ -1679,7 +1673,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"प्रवेशयोग्यता शॉर्टकटने <xliff:g id="SERVICE_NAME">%1$s</xliff:g> बंद केली"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> वापरण्यासाठी दोन्ही व्हॉल्युम की तीन सेकंद दाबा आणि धरून ठेवा"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"तुम्ही अ‍ॅक्सेसिबिलिटी बटण दाबल्यावर वापरण्यासाठी वैशिष्ट्य निवडा:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्या सोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने दोन बोटांनी स्वाइप करा):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्यासोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने दोन बोटांनी स्वाइप करा):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्या सोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने तीन बोटांनी स्वाइप करा):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"सेवांदरम्यान स्विच करण्यासाठी, अ‍ॅक्सेसिबिलिटी बटणाला स्पर्श करा आणि धरून ठेवा."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"सेवांदरम्यान स्विच करण्यासाठी, दोन बोटांनी वरच्या दिशेला स्वाइप करा आणि धरून ठेवा."</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"वर्गीकरण न केलेले"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"तुम्ही या सूचनांचे महत्त्व सेट केले."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"सामील असलेल्या लोकांमुळे हे महत्वाचे आहे."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सह नवीन वापरकर्ता तयार करण्याची <xliff:g id="APP">%1$s</xliff:g> ला अनुमती द्यायची?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सह नवीन वापरकर्ता तयार करण्याची (हे खाते असलेला वापरकर्ता आधीपासून विद्यमान आहे) <xliff:g id="APP">%1$s</xliff:g> ला अनुमती द्यायची?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"एक भाषा जोडा"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"प्रदेश प्राधान्य"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषा नाव टाइप करा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index ed5e1fb..35e3658 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ketik untuk melihat semua rangkaian"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Sambung"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua rangkaian"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Benarkan rangkaian Wi-Fi yang dicadangkan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rangkaian yang dicadangkan oleh <xliff:g id="NAME">%s</xliff:g>. Peranti mungkin disambungkan secara automatik."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Benarkan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Tidak perlu"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan dihidupkan secara automatik"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Apabila anda berada berdekatan dengan rangkaian disimpan yang berkualiti tinggi"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan hidupkan kembali"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Tidak dikategorikan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Anda menetapkan kepentingan pemberitahuan ini."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Mesej ini penting disebabkan orang yang terlibat."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Benarkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baharu dengan <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Benarkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baharu dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> (Pengguna dengan akaun ini sudah wujud)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tambahkan bahasa"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Pilihan wilayah"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Taipkan nama bahasa"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 09dc818..d69b7d1 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ကွန်ရက်အားလုံးကို ကြည့်ရန် တို့ပါ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ချိတ်ဆက်ရန်"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ကွန်ရက်အားလုံး"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"အကြံပြုထားသည့် Wi‑Fi ကွန်ရက်များ ခွင့်ပြုမလား။"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> သည် ကွန်ရက်များကို အကြံပြုထားသည်။ စက်သည် အလိုအလျောက် ချိတ်ဆက်နိုင်သည်။"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ခွင့်ပြုရန်"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"မလိုပါ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ကို အလိုအလျောက်​ ပြန်ဖွင့်ပေးလိမ့်ပါမည်"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"သိမ်းဆည်းထားသည့် အရည်အသွေးမြင့်ကွန်ရက်များအနီးသို့ ရောက်ရှိသည့်အခါ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ပြန်မဖွင့်ပါနှင့်"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"အမျိုးအစားမခွဲရသေးပါ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ဤသတိပေးချက်များ၏ အရေးပါမှုကိုသတ်မှတ်ပြီးပါပြီ။"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ပါဝင်သည့်လူများကြောင့် အရေးပါပါသည်။"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ကို <xliff:g id="ACCOUNT">%2$s</xliff:g> ဖြင့်အသုံးပြုသူအသစ်ဖန်တီးခွင့်ပြုမလား။"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> ကို <xliff:g id="ACCOUNT">%2$s</xliff:g> ဖြင့်အသုံးပြုသူအသစ် ဖန်တီးခွင့်ပြုမလား (ဤအကောင့်ဖြင့် အသုံးပြုသူ ရှိနှင့်ပြီးဖြစ်သည်)။"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ဘာသာစကားတစ်ခု ထည့်ပါ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ဒေသရွေးချယ်မှု"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ဘာသာစကားအမည် ထည့်ပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0ad63bf..a597170 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Vil du gi &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilgang til kalenderen din?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"sende og lese SMS-meldinger"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; sende og se SMS-meldinger?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; sende og se tekstmeldinger?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, medieinnhold og filer på enheten din"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Vil du gi &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilgang til bilder, medier og filer på enheten din?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trykk for å se alle nettverkene"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koble til"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle nettverk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vil du tillate foreslåtte Wi-Fi nettverk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>-foreslåtte nettverk. Enheten kan koble til automatisk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillat"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nei takk"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi slås på automatisk"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheten av et lagret nettverk av høy kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ikke slå på igjen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uten kategori"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du angir viktigheten for disse varslene."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dette er viktig på grunn av folkene som er involvert."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vil du la <xliff:g id="APP">%1$s</xliff:g> opprette en ny bruker med <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vil du la <xliff:g id="APP">%1$s</xliff:g> opprette en ny bruker med <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Det finnes allerede en bruker med denne kontoen.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Legg til et språk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regionsinnstilling"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Skriv inn språknavn"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index d0f6ae43..0cbaaa0 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"यो अनुप्रयोगले तपाईंको शारीरिक गतिविधिको पहिचान गर्न सक्छ।"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"तस्बिरहरू र भिडियोहरू लिनुहोस्।"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"अनुप्रयोग वा सेवालाई तस्बिर र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै समय प्रणालीको क्यामेरा प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA सम्बन्धी अनुमति हुनु पर्छ"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"कम्पन नियन्त्रण गर्नुहोस्"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"अनुप्रयोगलाई भाइब्रेटर नियन्त्रण गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"फोन नम्बरहरूमा सिधै कल गर्नुहोस्"</string>
@@ -1265,14 +1263,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सबै नेटवर्कहरू हेर्न ट्याप गर्नुहोस्"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"जडान गर्नुहोस्"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सबै नेटवर्कहरू"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सिफारिस गरिएका Wi‑Fi नेटवर्कहरूलाई अनुमति दिनुहोस्?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ले सिफारिस गरेका नेटवर्कहरू। यन्त्र स्वतः जडान हुन सक्छ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमति दिनुहोस्"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"पर्दैन, धन्यवाद"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi स्वतः सक्रिय हुनेछ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तपाईं कुनै सुरक्षित गरिएको उच्च गुणस्तरीय नेटवर्कको नजिक हुनुभएको अवस्थामा"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"फेरि सक्रिय नगर्नुहोला"</string>
@@ -1902,8 +1896,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"वर्गीकरण नगरिएको"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"तपाईंले यी सूचनाहरूको महत्त्व सेट गर्नुहोस् ।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"यसमा सङ्लग्न भएका मानिसहरूको कारणले गर्दा यो महत्वपूर्ण छ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सँगै नयाँ प्रयोगकर्ता सिर्जना गर्न <xliff:g id="APP">%1$s</xliff:g> लाई अनुमति दिने हो?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सँगै नयाँ प्रयोगकर्ता सिर्जना गर्न <xliff:g id="APP">%1$s</xliff:g> लाई अनुमति दिने (यस खाताको प्रयोगकर्ता पहिले नै अवस्थित छ) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"भाषा थप्नुहोस्"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"क्षेत्रको प्राथमिकता"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषाको नाम टाइप गर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 1abad6d..e898389 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerken te bekijken"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinding maken"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netwerken"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Voorgestelde wifi-netwerken toestaan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> heeft netwerken voorgesteld. Apparaat kan automatisch verbinding maken."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Toestaan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nee, bedankt"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wifi wordt automatisch ingeschakeld"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer je in de buurt van een opgeslagen netwerk van hoge kwaliteit bent"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Niet weer inschakelen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Geen categorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Je stelt het belang van deze meldingen in."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dit is belangrijk vanwege de betrokken mensen."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Toestaan dat <xliff:g id="APP">%1$s</xliff:g> een nieuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> maakt?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Toestaan dat <xliff:g id="APP">%1$s</xliff:g> een nieuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> maakt (er is al een gebruiker met dit account)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Een taal toevoegen"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiovoorkeur"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Typ een taalnaam"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 425f0ac..bd6d2cc 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ସମସ୍ତ ନେଟ୍‌ୱର୍କ ଦେଖିବାକୁ ଟାପ୍‍ କରନ୍ତୁ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"କନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ସମସ୍ତ ନେଟ୍‌ୱର୍କ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ପ୍ରସ୍ତାବିତ ୱାଇ-ଫାଇ ନେଟ୍‌ୱାର୍କଗୁଡ଼ିକୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ପ୍ରସ୍ତାବିତ ନେଟ୍‌ୱାର୍କଗୁଡ଼ିକ। ଡିଭାଇସ୍ ହୁଏତ ସ୍ୱଚାଳିତ ଭାବେ ସଂଯୋଗ ହୋଇପାରେ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ନାହିଁ, ଥାଉ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ୱାଇ-ଫାଇ ସ୍ୱଚାଳିତ ଭାବେ ଅନ୍ ହେବ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ଆପଣ ଏକ ଉଚ୍ଚ-କ୍ୱାଲିଟୀ ବିଶିଷ୍ଟ ସେଭ୍‌ କରାଯାଇଥିବା ନେଟ୍‌ୱର୍କ ପାଖରେ ଥିବା ସମୟରେ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ପୁଣି ଅନ୍‍ କରନ୍ତୁ ନାହିଁ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ଅବର୍ଗୀକୃତ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ପ୍ରମୁଖତା ଆପଣ ସେଟ୍‍ କରନ୍ତି।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ସମ୍ପୃକ୍ତ ଲୋକଙ୍କ କାରଣରୁ ଏହା ଗୁରୁତ୍ୱପୂର୍ଣ୍ଣ ଅଟେ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ସହ ଏକ ନୂଆ ୟୁଜର୍‌ ତିଆରି କରିବା ପାଇଁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ସହ ଏକ ନୂଆ ୟୁଜର୍‌ ତିଆରି କରିବାକୁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ (ଏହି ଆକାଉଣ୍ଟରେ ଜଣେ ୟୁଜର୍‌ ପୂର୍ବରୁ ରହିଛନ୍ତି)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ଏକ ଭାଷା ଯୋଡ଼ନ୍ତୁ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ପସନ୍ଦର ଅଞ୍ଚଳ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ଭାଷାର ନାମ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index c3b2ca8..a9e93d4 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ਸਾਰੇ ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ਸਾਰੇ ਨੈੱਟਵਰਕ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ਕੀ ਸੁਝਾਏ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਵਰਤਣੇ ਹਨ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ਦੇ ਸੁਝਾਏ ਨੈੱਟਵਰਕ। ਸ਼ਾਇਦ ਡੀਵਾਈਸ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਹੋਵੇ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ਵਰਤਣ ਦਿਓ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ਵਾਈ‑ਫਾਈ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੱਲ ਪਵੇਗਾ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਰੱਖਿਅਤ ਕੀਤੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਨੈੱਟਵਰਕ ਦੇ ਨੇੜੇ ਹੋਵੋ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ਵਾਪਸ ਚਾਲੂ ਨਾ ਕਰੋ"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ਗੈਰ-ਸ਼੍ਰੇਣੀਕਿਰਤ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ਤੁਸੀਂ ਇਹਨਾਂ ਸੂਚਨਾਵਾਂ ਦੀ ਮਹੱਤਤਾ ਸੈੱਟ ਕੀਤੀ।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ਇਹ ਸ਼ਾਮਲ ਲੋਕਾਂ ਦੇ ਕਾਰਨ ਮਹੱਤਵਪੂਰਨ ਹੈ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"ਕੀ <xliff:g id="APP">%1$s</xliff:g> ਨੂੰ <xliff:g id="ACCOUNT">%2$s</xliff:g> ਨਾਲ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਉਣ ਦੀ ਮਨਜ਼ੂਰੀ ਦੇਣੀ ਹੈ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"ਕੀ <xliff:g id="APP">%1$s</xliff:g> ਨੂੰ <xliff:g id="ACCOUNT">%2$s</xliff:g> ਨਾਲ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਉਣ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ (ਇਸ ਖਾਤੇ ਨਾਲ ਇੱਕ ਵਰਤੋਂਕਾਰ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਮੌਜੂਦ ਹੈ) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ਇੱਕ ਭਾਸ਼ਾ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ਖੇਤਰ ਤਰਜੀਹ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ਭਾਸ਼ਾ ਨਾਮ ਟਾਈਪ ਕਰੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 65058a3..537089c 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Kliknij, by zobaczyć wszystkie sieci"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Połącz"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Wszystkie sieci"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Zezwalać na sugerowane sieci Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Sugerowane sieci: <xliff:g id="NAME">%s</xliff:g>. Urządzenie może łączyć się automatycznie."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Zezwól"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nie, dziękuję"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi włączy się automatycznie"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gdy znajdziesz się w pobliżu zapisanej sieci o mocnym sygnale"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nie włączaj ponownie"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Bez kategorii"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ustawiłeś ważność tych powiadomień."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ta wiadomość jest ważna ze względu na osoby uczestniczące w wątku."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Zezwalasz aplikacji <xliff:g id="APP">%1$s</xliff:g> na utworzenie nowego użytkownika dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Zezwalasz aplikacji <xliff:g id="APP">%1$s</xliff:g> na utworzenie nowego użytkownika dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g>)? Użytkownik z tym kontem już istnieje."</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodaj język"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Ustawienie regionu"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Wpisz nazwę języka"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 5c9bed4..ba8285f 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
@@ -1482,7 +1478,7 @@
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
     <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"O app para carro está sendo usado"</string>
     <string name="car_mode_disable_notification_message" msgid="7647248420931129377">"Toque para sair do app para carro."</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"Ponto de acesso ou tethering ativo"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Tethering desativado"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Fale com seu administrador para saber detalhes"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"O atalho de acessibilidade desativou o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Toque nos dois botões de volume e os mantenha pressionados por três segundo para usar o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Escolha um serviço a ser usado quando você toca no botão Acessibilidade:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para alternar entre serviços, toque no botão de acessibilidade e mantenha-o pressionado."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para alternar entre serviços, deslize de baixo para cima na tela com dois dedos sem soltar."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para alternar entre serviços, deslize de baixo para cima na tela com três dedos sem soltar."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem classificação"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Você definiu a importância dessas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Isso é importante por causa das pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um usuário com essa conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index ba28c60..c7df3a7 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ligar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Pretende permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas por <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode estabelecer ligação automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não, obrigado"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando estiver próximo de uma rede de alta qualidade guardada."</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não reativar"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Acessório de áudio analógico detetado"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"O dispositivo ligado não é compatível com este telemóvel. Toque para saber mais."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB ligada"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toque para desativar a depuração USB."</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toque para desativar a depuração USB"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecione para desativar a depuração por USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Modo de estrutura de teste ativado"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Efetue uma reposição de dados de fábrica para desativar o Modo de estrutura de teste."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem categoria"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Definiu a importância destas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"É importante devido às pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Pretende permitir que o <xliff:g id="APP">%1$s</xliff:g> crie um novo utilizador com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Pretende permitir que o <xliff:g id="APP">%1$s</xliff:g> crie um novo utilizador com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um utilizador com esta conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Intr. nome do idioma"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 5c9bed4..ba8285f 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
@@ -1482,7 +1478,7 @@
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
     <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"O app para carro está sendo usado"</string>
     <string name="car_mode_disable_notification_message" msgid="7647248420931129377">"Toque para sair do app para carro."</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"Ponto de acesso ou tethering ativo"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Tethering desativado"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Fale com seu administrador para saber detalhes"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"O atalho de acessibilidade desativou o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Toque nos dois botões de volume e os mantenha pressionados por três segundo para usar o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Escolha um serviço a ser usado quando você toca no botão Acessibilidade:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para alternar entre serviços, toque no botão de acessibilidade e mantenha-o pressionado."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para alternar entre serviços, deslize de baixo para cima na tela com dois dedos sem soltar."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para alternar entre serviços, deslize de baixo para cima na tela com três dedos sem soltar."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem classificação"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Você definiu a importância dessas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Isso é importante por causa das pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um usuário com essa conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 4386c12..7277228 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Atingeți pentru a vedea toate rețelele"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectați-vă"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Toate rețelele"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permiteți rețelele Wi-Fi sugerate?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> a sugerat rețele. Dispozitivul se poate conecta automat."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permiteți"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nu, mulțumesc"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se va activa automat"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Când vă aflați lângă o rețea salvată, de înaltă calitate"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nu reactivați"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Neclasificate"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Dvs. setați importanța acestor notificări."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Notificarea este importantă având în vedere persoanele implicate."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permiteți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permiteți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>? (există deja un utilizator cu acest cont)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adăugați o limbă"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiunea preferată"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Numele limbii"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 13d75e3..e458e46 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Нажмите, чтобы увидеть список сетей"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Подключиться"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Все сети"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Подключаться к предложенным сетям Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Приложение \"<xliff:g id="NAME">%s</xliff:g>\" рекомендует сети, к которым устройство может подключаться автоматически."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Разрешить"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Нет, спасибо"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi включится автоматически"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Когда вы будете в зоне действия сохраненной сети с хорошим сигналом."</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не включать снова"</string>
@@ -1724,8 +1720,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Сервис <xliff:g id="SERVICE_NAME">%1$s</xliff:g> отключен"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Чтобы использовать сервис \"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\", нажмите и удерживайте обе клавиши громкости в течение трех секунд."</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Выберите сервис, который будет запускаться при нажатии кнопки специальных возможностей:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберите сервис, который будет запускаться жестом для доступа к специальным возможностям (провести по экрану снизу вверх двумя пальцами):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберите сервис, который будет запускаться жестом для доступа к специальным возможностям (провести по экрану снизу вверх тремя пальцами):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберите сервис, который будет запускаться жестом (провести по экрану снизу вверх двумя пальцами):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберите сервис, который будет запускаться жестом (провести по экрану снизу вверх тремя пальцами):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Для переключения между сервисами нажмите и удерживайте кнопку специальных возможностей."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Для переключения между сервисами проведите по экрану снизу вверх двумя пальцами и задержите их."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Для переключения между сервисами проведите по экрану снизу вверх тремя пальцами и задержите их."</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Без категории"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Вы определяете важность этих уведомлений."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Важное (люди)"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" создать пользователя для аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" создать нового пользователя для аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g> (пользователь c таким аккаунтом уже есть)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Добавьте язык"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Региональные настройки"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Введите язык"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 648f319..3e0f582 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1259,14 +1259,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"සියලු ජාල බැලීමට තට්ටු කරන්න"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"සම්බන්ධ කරන්න"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"සියලු ජාල"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"යෝජිත Wi-Fi ජාල ඉඩ දෙන්නද?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> යෝජිත ජාල. උපාංගය ස්වයංක්‍රියව සම්බන්ධ වනු ඇත."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ඉඩ දෙන්න"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"එපා, ස්තූතියි"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ස්වයංක්‍රියව ක්‍රියාත්මක වනු ඇත"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ඔබ උසස් තත්ත්වයේ සුරැකි ජාලයක් අවට සිටින විට"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"නැවත ක්‍රියාත්මක නොකරන්න"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"වර්ගීකරණය නොකළ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ඔබ මෙම දැනුම්දීම්වල වැදගත්කම සකසා ඇත."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"සම්බන්ධ වූ පුද්ගලයන් නිසා මෙය වැදගත් වේ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> හට <xliff:g id="ACCOUNT">%2$s</xliff:g> සමගින් නව පරිශීලකයෙකු සෑදීමට ඉඩ දෙන්නද?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> හට <xliff:g id="ACCOUNT">%2$s</xliff:g> සමගින් නව පරිශීලකයෙකු සෑදීමට ඉඩ දෙන්නද (මෙම ගිණුම සහිත පරිශීලකයෙකු දැනටමත් සිටී) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"භාෂාවක් එක් කරන්න"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ප්‍රදේශ මනාපය"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"භාෂා නම ටයිප් කරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 0202efd..015cd91 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všetky siete"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Pripojiť"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Všetky siete"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Chcete povoliť navrhované siete Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Siete navrhuje aplikácia <xliff:g id="NAME">%s</xliff:g>. Zariadenie sa môže pripájať automaticky."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Povoliť"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nie, ďakujem"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi sa zapne automaticky"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Keď budete v blízkosti kvalitnej uloženej siete"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znova nezapínať"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizované"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Nastavili ste dôležitosť týchto upozornení."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Táto správa je dôležitá vzhľadom na osoby, ktorých sa to týka."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> vytvoriť nového používateľa pomocou účtu <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> vytvoriť nového používateľa pomocou účtu <xliff:g id="ACCOUNT">%2$s</xliff:g> (používateľ s týmto účtom už existuje)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pridať jazyk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferovaný región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Zadajte názov jazyka"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 4bcdadd..54da1dc 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1063,10 +1063,10 @@
       <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> minutami</item>
     </plurals>
     <plurals name="duration_hours_relative" formatted="false" msgid="676894109982008411">
-      <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> uro</item>
-      <item quantity="two">pred <xliff:g id="COUNT_1">%d</xliff:g> urama</item>
-      <item quantity="few">pred <xliff:g id="COUNT_1">%d</xliff:g> urami</item>
-      <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> urami</item>
+      <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="two">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="few">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
     </plurals>
     <plurals name="duration_days_relative" formatted="false" msgid="2203515825765397130">
       <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> dnevom</item>
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dotaknite se, če si želite ogledati vsa omrežja"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Vzpostavi povezavo"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Vsa omrežja"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite dovoliti predlagana omrežja Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> – predlagana omrežja. Naprava se lahko poveže samodejno."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dovoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Povezava Wi‑Fi se bo samodejno vklopila"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Ko ste v bližini zanesljivega shranjenega omrežja"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne vklopi znova"</string>
@@ -1405,7 +1401,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Zaznana je analogna dodatna zvočna oprema"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Priključena naprava ni združljiva s tem telefonom. Dotaknite se za več informacij."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Iskanje napak prek USB je povezano"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Dotaknite se, če želite izklopiti odpravljanje napak prek USB-ja"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Dotaknite se, če želite izklop. odpravlj. napak prek USB-ja"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Izberite, če želite onemogočiti iskanje in odpravljanje napak prek vrat USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Način preizkusnega ogrodja je omogočen"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Če želite onemogočiti način preizkusnega ogrodja, ponastavite napravo na tovarniške nastavitve."</string>
@@ -1543,7 +1539,7 @@
     </plurals>
     <string name="action_mode_done" msgid="7217581640461922289">"Končano"</string>
     <string name="progress_erasing" msgid="2569962663843586562">"Brisanje skupne shrambe …"</string>
-    <string name="share" msgid="1778686618230011964">"Deli z dr."</string>
+    <string name="share" msgid="1778686618230011964">"Deli"</string>
     <string name="find" msgid="4808270900322985960">"Najdi"</string>
     <string name="websearch" msgid="4337157977400211589">"Spletno iskanje"</string>
     <string name="find_next" msgid="5742124618942193978">"Najdi naslednje"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizirano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi določite raven pomembnosti teh obvestil."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Pomembno zaradi udeleženih ljudi."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Dovolite, da aplikacija <xliff:g id="APP">%1$s</xliff:g> ustvari novega uporabnika za račun <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Dovolite aplikaciji <xliff:g id="APP">%1$s</xliff:g>, da ustvari novega uporabnika za račun <xliff:g id="ACCOUNT">%2$s</xliff:g> (uporabnik s tem računom že obstaja)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajanje jezika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Nastavitev območja"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Vnesite ime jezika"</string>
@@ -2057,7 +2055,7 @@
     <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Bližnjice ni bilo mogoče obnoviti zaradi neujemanja podpisa aplikacije"</string>
     <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Bližnjice ni bilo mogoče obnoviti"</string>
     <string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"Bližnjica je onemogočena"</string>
-    <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODSTRANI"</string>
+    <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODMESTI"</string>
     <string name="harmful_app_warning_open_anyway" msgid="596432803680914321">"VSEENO ODPRI"</string>
     <string name="harmful_app_warning_title" msgid="8982527462829423432">"Zaznana je bila škodljiva aplikacija"</string>
     <string name="slices_permission_request" msgid="8484943441501672932">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index bc8d380..48ae4d7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trokit për të parë të gjitha rrjetet"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Lidhu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Të gjitha rrjetet"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Të lejohen rrjetet e sugjeruara Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rrjet e sugjeruara të <xliff:g id="NAME">%s</xliff:g>. Pajisja mund të lidhet automatikisht."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Lejo"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Jo, faleminderit"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi do të aktivizohet automatikisht"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kur ndodhesh pranë një rrjeti të ruajtur me cilësi të lartë"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Mos e aktivizo përsëri"</string>
@@ -1679,7 +1675,7 @@
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Zgjidh një shërbim për ta përdorur kur troket butonin e qasshmërisë:"</string>
     <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Zgjidh një shërbim për ta përdorur me gjestin e qasshmërisë (rrëshqit shpejt lart nga fundi i ekranit me dy gishta):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Zgjidh një shërbim për ta përdorur me gjestin e qasshmërisë (rrëshqit shpejt lart nga fundi i ekranit me tre gishta):"</string>
-    <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Për të kaluar mes shërbimeve, prek dhe mbaj prekur butonin e qasshmërisë."</string>
+    <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Për të kaluar mes shërbimeve, prek dhe mbaj të shtypur butonin e qasshmërisë."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Për të kaluar mes pajisjeve, rrëshqit shpejt lart me dy gishta dhe mbaje prekur."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Për të kaluar mes pajisjeve, rrëshqit shpejt lart me tre gishta dhe mbaje prekur."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Zmadhimi"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"E pakategorizuara"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ke caktuar rëndësinë e këtyre njoftimeve."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Është i rëndësishëm për shkak të personave të përfshirë."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Të lejohet <xliff:g id="APP">%1$s</xliff:g> që të krijojë një përdorues të ri me <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Të lejohet <xliff:g id="APP">%1$s</xliff:g> që të krijojë një përdorues të ri me <xliff:g id="ACCOUNT">%2$s</xliff:g> (një përdorues me këtë llogari ekziston tashmë) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Shto një gjuhë"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferenca e rajonit"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Shkruaj emrin e gjuhës"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f4f29a1..75fc1ce 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Додирните да бисте видели све мреже"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Повежи"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Све мреже"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Желите да дозволите предложене Wi‑Fi мреже?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Мреже које предлаже <xliff:g id="NAME">%s</xliff:g>. Уређај ће се можда повезати аутоматски."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволи"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, хвала"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ће се аутоматски укључити"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Када сте у близини сачуване мреже високог квалитета"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не укључуј поново"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризовано"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ви подешавате важност ових обавештења."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ово је важно због људи који учествују."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Желите ли да дозволите апликацији <xliff:g id="APP">%1$s</xliff:g> да направи новог корисника за <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Желите ли да дозволите апликацији <xliff:g id="APP">%1$s</xliff:g> да направи новог корисника за <xliff:g id="ACCOUNT">%2$s</xliff:g> (корисник са овим налогом већ постоји)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додајте језик"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Подешавање региона"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Унесите назив језика"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index ccd80ad..fdd0b7b 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryck för att visa alla nätverk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Anslut"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alla nätverk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vill du tillåta föreslagna Wi-Fi-nätverk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Nätverk som föreslagits av <xliff:g id="NAME">%s</xliff:g>. Enheten kan anslutas automatiskt."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillåt"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nej tack"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi aktiveras automatiskt"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"När du är i närheten av ett sparat nätverk av hög kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Återaktivera inte"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Okategoriserad"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du anger hur viktiga aviseringarna är."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Detta är viktigt på grund av personerna som deltar."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Tillåter du att <xliff:g id="APP">%1$s</xliff:g> skapar en ny användare för <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Tillåter du att <xliff:g id="APP">%1$s</xliff:g> skapar en ny användare för <xliff:g id="ACCOUNT">%2$s</xliff:g> (det finns redan en användare med det här kontot)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Lägg till ett språk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regionsinställningar"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ange språket"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 4439f2c..c8c582f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gusa ili uone mitandao yote"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Unganisha"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Mitandao yote"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Ungependa kuruhusu mitandao inayopendekezwa ya Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mitandao inayopendekezwa kwa ajili ya <xliff:g id="NAME">%s</xliff:g>. Huenda kifaa kikaunganisha kiotomatiki."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Ruhusu"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hapana, asante"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi itawashwa kiotomatiki"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Unapokuwa karibu na mtandao uliohifadhiwa wenye ubora wa juu"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Usiwashe tena"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ambazo aina haijabainishwa"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Uliweka mipangilio ya umuhimu wa arifa hizi."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Hii ni muhimu kwa sababu ya watu waliohusika."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Ungependa kuruhusu <xliff:g id="APP">%1$s</xliff:g> iunde Mtumiaji mpya ikitumia <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Ungependa kuruhusu <xliff:g id="APP">%1$s</xliff:g> iunde Mtumiaji mpya ikitumia <xliff:g id="ACCOUNT">%2$s</xliff:g> (Je, akaunti hii tayari ina Mtumiaji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ongeza lugha"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Mapendeleo ya eneo"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Weka jina la lugha"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index fec5c5f..0956aad 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -341,9 +341,9 @@
     <string name="permlab_answerPhoneCalls" msgid="4077162841226223337">"ஃபோன் அழைப்புகளுக்குப் பதிலளி"</string>
     <string name="permdesc_answerPhoneCalls" msgid="2901889867993572266">"உள்வரும் ஃபோன் அழைப்பிற்குப் பதிலளிக்க, ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_receiveSms" msgid="8673471768947895082">"உரைச் செய்திகளை (SMS) பெறுதல்"</string>
-    <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிப்பதற்கு அல்லது நீக்குவதற்குப் பயன்பாட்டால் முடியும் என்பதாகும்."</string>
+    <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிப்பதற்கு அல்லது நீக்குவதற்கு ஆப்ஸால் முடியும் என்பதாகும்."</string>
     <string name="permlab_receiveMms" msgid="1821317344668257098">"உரைச் செய்திகளை (MMS) பெறுதல்"</string>
-    <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்கவோ, நீக்கவோ பயன்பாட்டால் முடியும் என்பதாகும்."</string>
+    <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்கவோ, நீக்கவோ ஆப்ஸால் முடியும் என்பதாகும்."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"செல் அலைபரப்புச் செய்திகளைப் படித்தல்"</string>
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"உங்கள் சாதனத்தில் பெறப்படும் செல் அலைபரப்புச் செய்திகளைப் படிப்பதற்குப் ஆப்ஸை அனுமதிக்கிறது. அவசரநிலை சூழ்நிலைகளை உங்களுக்கு எச்சரிக்கைச் செய்வதற்கு சில இடங்களில் செல் அலைபரப்பு விழிப்பூட்டல்கள் வழங்கப்படும். அவசரநிலை மொபைல் அலைபரப்புப் பெறப்படும்போது உங்கள் சாதனத்தின் செயல்திறன் அல்லது செயல்பாட்டுடன் தீங்கிழைக்கும் பயன்பாடுகள் அதைத் தடுக்கலாம்."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"குழுசேர்ந்த ஊட்டங்களைப் படித்தல்"</string>
@@ -356,18 +356,18 @@
     <string name="permdesc_readSms" product="default" msgid="6826832415656437652">"இந்த ஆப்ஸ் உங்கள் மொபைலில் சேமிக்கப்பட்டுள்ள எல்லா SMS (உரை) செய்திகளையும் படிக்கலாம்."</string>
     <string name="permlab_receiveWapPush" msgid="5991398711936590410">"உரைச் செய்திகளைப் (WAP) பெறுதல்"</string>
     <string name="permdesc_receiveWapPush" msgid="748232190220583385">"WAP செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. உங்களுக்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்க அல்லது நீக்குவதற்கான திறன் இந்த அனுமதியில் உள்ளடங்கும்."</string>
-    <string name="permlab_getTasks" msgid="6466095396623933906">"இயங்கும் பயன்பாடுகளை மீட்டெடுத்தல்"</string>
+    <string name="permlab_getTasks" msgid="6466095396623933906">"இயங்கும் ஆப்ஸை மீட்டெடுத்தல்"</string>
     <string name="permdesc_getTasks" msgid="7454215995847658102">"நடப்பில் மற்றும் சமீபத்தில் இயங்கும் காரியங்களின் தகவலைப் பெற ஆப்ஸை அனுமதிக்கிறது. சாதனத்தில் எந்தப் பயன்பாடுகள் பயன்படுத்தப்படுகின்றன என்பது குறித்த தகவலைக் கண்டறிய ஆப்ஸை இது அனுமதிக்கலாம்."</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="7918181259098220004">"சுயவிவரத்தையும் சாதன உரிமையாளர்களையும் நிர்வகித்தல்"</string>
-    <string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"சுயவிவர உரிமையாளர்களையும் சாதன உரிமையாளரையும் அமைக்க, பயன்பாடுகளை அனுமதிக்கிறது."</string>
-    <string name="permlab_reorderTasks" msgid="2018575526934422779">"இயங்கும் பயன்பாடுகளை மறுவரிசைப்படுத்தல்"</string>
+    <string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"சுயவிவர உரிமையாளர்களையும் சாதன உரிமையாளரையும் அமைக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
+    <string name="permlab_reorderTasks" msgid="2018575526934422779">"இயங்கும் ஆப்ஸை மறுவரிசைப்படுத்தல்"</string>
     <string name="permdesc_reorderTasks" msgid="7734217754877439351">"பின்புலத்திலும், முன்புலத்திலும் காரியங்களை நகர்த்த ஆப்ஸை அனுமதிக்கிறது. உங்கள் உள்ளீடு இல்லாமலே ஆப்ஸ் இதைச் செய்யலாம்."</string>
     <string name="permlab_enableCarMode" msgid="5684504058192921098">"கார் பயன்முறையை இயக்குதல்"</string>
     <string name="permdesc_enableCarMode" msgid="4853187425751419467">"கார் முறையை இயக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
-    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"பிற பயன்பாடுகளை மூடுதல்"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற பயன்பாடுகளின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
-    <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே தோன்றலாம்"</string>
-    <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
+    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"பிற ஆப்ஸை மூடுதல்"</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற ஆப்ஸின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
+    <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே தோன்றலாம்"</string>
+    <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
     <string name="permlab_runInBackground" msgid="7365290743781858803">"பின்னணியில் இயக்கு"</string>
     <string name="permdesc_runInBackground" msgid="7370142232209999824">"இந்த ஆப்ஸ், பின்னணியில் இயங்கலாம். இதனால் பேட்டரி விரைவாகத் தீர்ந்துவிடக்கூடும்."</string>
     <string name="permlab_useDataInBackground" msgid="8694951340794341809">"பின்னணியில் தரவைப் பயன்படுத்து"</string>
@@ -391,13 +391,13 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="5029460344724532288">"வலைபரப்பு முடிந்த பின்னரும் தங்கிவிடும் ஸ்டிக்கி வலைபரப்புகளை அனுப்ப ஆப்ஸை அனுமதிக்கும். அளவுக்கதிகமான உபயோகம் Android TVயின் வேகத்தைக் குறைக்கவோ நிலையற்றதாகவோ ஆக்கக்கூடும். இதனால் அதிகமான நினைவகம் பயன்படுத்தப்படும்."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"அலைபரப்பு முடிந்த பின்னும் இருக்கும், தொடர்ந்து அணுகத்தக்க அலைபரப்பை அனுப்பப் ஆப்ஸை அனுமதிக்கிறது. அதிகமாகப் பயன்படுத்தினால், மொபைலானது நினைவகத்தை மிக அதிகமாகப் பயன்படுத்துவதால் வேகம் குறைந்ததாகவும், நிலையற்றதாகவும் ஆகலாம்."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"உங்கள் தொடர்புகளைப் படித்தல்"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உட்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க பயன்பாடுகளை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உட்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
     <string name="permdesc_readContacts" product="tv" msgid="3890061004911027912">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவைத் தெரிந்துகொள்ள ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தனிநபரை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். இது உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கும், அத்துடன் தீங்குவிளைவிக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்புத் தரவைப் பகிரக்கூடும்."</string>
-    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்ட எண்ணிக்கை உட்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க பயன்பாடுகளை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
+    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்ட எண்ணிக்கை உட்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
     <string name="permlab_writeContacts" msgid="5107492086416793544">"உங்கள் தொடர்புகளை மாற்றுதல்"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்கப் பயன்பாடுகளை அனுமதிக்கிறது."</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="307929337692573341">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தொடர்பை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். தொடர்புத் தரவை நீக்க ஆப்ஸை இது அனுமதிக்கும்."</string>
-    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்கப் பயன்பாடுகளை அனுமதிக்கிறது."</string>
+    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"அழைப்புப் பதிவைப் படித்தல்"</string>
     <string name="permdesc_readCallLog" msgid="3204122446463552146">"இந்த ஆப்ஸ் உங்கள் அழைப்பு வரலாற்றைப் படிக்கலாம்."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"அழைப்புப் பதிவை எழுதுதல்"</string>
@@ -405,7 +405,7 @@
     <string name="permdesc_writeCallLog" product="tv" msgid="7939219462637746280">"உள்வரும், வெளிச்செல்லும் அழைப்புகள் குறித்த தகவல் உட்பட உங்கள் Android TVயின் அழைப்புப் பதிவைத் திருத்த ஆப்ஸை அனுமதிக்கும். உங்கள் அழைப்புப் பதிவை அழிக்கவோ திருத்தவோ தீங்கு விளைவிக்கும் ஆப்ஸ் இதைப் பயன்படுத்தக்கூடும்."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"உள்வரும் மற்றும் வெளிச்செல்லும் அழைப்புகள் குறித்த தகவல் உள்பட உங்கள் மொபைல் அழைப்புப் பதிவைத் திருத்துவதற்குப் ஆப்ஸை அனுமதிக்கிறது. உங்கள் அழைப்பின் பதிவை அழிக்க அல்லது திருத்த தீங்கு விளைவிக்கும் பயன்பாடுகள் இதைப் பயன்படுத்தலாம்."</string>
     <string name="permlab_bodySensors" msgid="4683341291818520277">"உடல் உணர்விகளை (இதயத் துடிப்பு மானிட்டர்கள் போன்றவை) அணுகுதல்"</string>
-    <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"உங்கள் இதயத்துடிப்பு விகிதம் போன்ற உங்கள் உடல்நிலையைக் கண்காணிக்கும் உணர்விகளில் இருந்து தரவை அணுக பயன்பாடுகளை அனுமதிக்கும்."</string>
+    <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"உங்கள் இதயத்துடிப்பு விகிதம் போன்ற உங்கள் உடல்நிலையைக் கண்காணிக்கும் உணர்விகளில் இருந்து தரவை அணுக ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_readCalendar" msgid="6716116972752441641">"கேலெண்டர் நிகழ்வுகளையும் விவரங்களையும் படிக்கலாம்"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"இந்த ஆப்ஸ் உங்கள் டேப்லெட்டில் சேமிக்கப்பட்டுள்ள கேலெண்டர் நிகழ்வுகள் அனைத்தையும் படிக்கலாம், உங்கள் கேலெண்டர் தரவைப் பகிரலாம் அல்லது சேமிக்கலாம்."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="1066881547471014386">"உங்கள் Android TVயில் சேமித்துள்ள அனைத்துக் கேலெண்டர் நிகழ்வுகளையும் இந்த ஆப்ஸால் தெரிந்துகொள்ள முடியும். அத்துடன் உங்களின் கேலெண்டர் தரவைப் பகிரவும் சேமிக்கவும் முடியும்."</string>
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"படங்கள் மற்றும் வீடியோக்களை எடுத்தல்"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் கேமராவைப் பயன்படுத்தி படங்களை எடுக்கலாம், வீடியோக்களை ரெக்கார்டு செய்யலாம்."</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"படங்களையும் வீடியோக்களையும் எடுப்பதற்கு சிஸ்டம் கேமராக்களை அணுக ஆப்ஸையோ சேவையையோ அனுமதி"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"இந்த முன்னுரிமை பெற்ற சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். அதே போல android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"இந்த முன்னுரிமை பெற்ற சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"அதிர்வைக் கட்டுப்படுத்துதல்"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"அதிர்வைக் கட்டுப்படுத்தப் ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"தொலைபேசி எண்களை நேரடியாக அழைத்தல்"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"எல்லா நெட்வொர்க்குகளையும் பார்க்க, தட்டவும்"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"இணை"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"எல்லா நெட்வொர்க்குகளும்"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"பரிந்துரைக்கப்பட்ட வைஃபை நெட்வொர்க்குகளை அனுமதிக்க வேண்டுமா?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> பரிந்துரைக்கும் நெட்வொர்க்குகள். சாதனம் தானாக இணைக்கப்படக்கூடும்."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"அனுமதி"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"வேண்டாம்"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"வைஃபை தானாக ஆன் ஆகும்"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"சேமித்த, உயர்தர நெட்வொர்க்கிற்கு அருகில் இருக்கும்போது"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"மீண்டும் ஆன் செய்யாதே"</string>
@@ -1384,7 +1380,7 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"பிற ஆப்ஸின் மேலே காட்டு"</string>
-    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற பயன்பாடுகளின் மீது தோன்றுகிறது"</string>
+    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
     <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
     <string name="alert_windows_notification_message" msgid="8917232109522912560">"<xliff:g id="NAME">%s</xliff:g> இந்த அம்சத்தைப் பயன்படுத்த வேண்டாம் என நினைத்தால், அமைப்புகளைத் திறந்து அதை முடக்க, தட்டவும்."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="2902891971380544651">"ஆஃப் செய்"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"வகைப்படுத்தப்படாதவை"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"இந்த அறிவிப்புகளின் முக்கியத்துவத்தை அமைத்துள்ளீர்கள்."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ஈடுபட்டுள்ளவர்களின் காரணமாக, இது முக்கியமானது."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> மூலம் புதிய பயனரை உருவாக்க <xliff:g id="APP">%1$s</xliff:g>ஐ அனுமதிக்கவா?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> (இந்தக் கணக்கில் ஏற்கனவே ஒரு பயனர் உள்ளார்) மூலம் புதிய பயனரை உருவாக்க <xliff:g id="APP">%1$s</xliff:g>ஐ அனுமதிக்கவா?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"மொழியைச் சேர்"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"மண்டல விருப்பம்"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"மொழி பெயரை உள்ளிடுக"</string>
@@ -2008,7 +2006,7 @@
     <string name="standby_warning_message" product="default" msgid="5222741828239073484">"இந்தச் சாதனம் விரைவில் ஆஃப் ஆகலாம். இதைத் தொடர்ந்து ஆனில் வைக்கத் தட்டவும்."</string>
     <string name="notification_appops_camera_active" msgid="5050283058419699771">"கேமரா"</string>
     <string name="notification_appops_microphone_active" msgid="4335305527588191730">"மைக்ரோஃபோன்"</string>
-    <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற பயன்பாடுகளின் மேல் காட்டுகிறது"</string>
+    <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற ஆப்ஸின் மேல் காட்டுகிறது"</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2348803891571320452">"வழக்கமான பேட்டரி சேமிப்பானுக்கான விவர அறிவிப்பு"</string>
     <string name="dynamic_mode_notification_title" msgid="508815255807182035">"வழக்கமாகச் சார்ஜ் செய்வதற்கு முன்பே பேட்டரி தீர்ந்துபோகக்கூடும்"</string>
     <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"பேட்டரி நிலையை நீட்டிக்க பேட்டரி சேமிப்பான் இயக்கப்பட்டுள்ளது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index de1a8d6..a8550f8 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"మీ క్యాలెండర్‌ని యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ని అనుమతించాలా?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS సందేశాలను పంపడం మరియు వీక్షించడం"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"SMS సందేశాలు పంపడానికి మరియు వీక్షించడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ని అనుమతించాలా?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"SMS సందేశాలు పంపడం, చూడటం చేయగలిగేలా &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"నిల్వ"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"మీ పరికరంలోని ఫోటోలు, మీడియా మరియు ఫైల్‌లను యాక్సెస్ చేయడానికి"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"మీ పరికరంలోని ఫోటోలు, మీడియా మరియు ఫైల్‌లను యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"ఈ యాప్ మీ భౌతిక కార్యాకలాపాన్ని గుర్తించగలదు."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"చిత్రాలు మరియు వీడియోలు తీయడం"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"ఈ యాప్‌ కెమెరాను ఉపయోగించి ఎప్పుడైనా చిత్రాలను తీయగలదు మరియు వీడియోలను రికార్డ్ చేయగలదు."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ఫోటోలు, వీడియోలు తీయడానికి సిస్టమ్ కెమెరాలకు యాప్, లేదా సేవా యాక్సెస్‌ను అనుమతించండి"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"ఈ విశేష | సిస్టమ్ యాప్ ఎప్పుడైనా సిస్టమ్ కెమెరాను ఉపయోగించి ఫోటోలు తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు. యాప్‌కు android.permission.CAMERA అనుమతి ఇవ్వడం కూడా అవసరం"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"వైబ్రేషన్‌ను నియంత్రించడం"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"వైబ్రేటర్‌ను నియంత్రించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"నేరుగా కాల్ చేసే ఫోన్ నంబర్‌లు"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"అన్ని నెట్‌వర్క్‌లు చూడటానికి నొక్కండి"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"కనెక్ట్ చేయి"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"అన్ని నెట్‌వర్క్‌లు"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"సూచించిన Wi‑Fi నెట్‌వర్క్‌లను అనుమతించాలా?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> సూచించిన నెట్‌వర్క్‌లు. పరికరం ఆటోమేటిక్‌గా కనెక్ట్ అవచ్చు."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"అనుమతించు"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"వద్దు"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi స్వయంచాలకంగా ఆన్ అవుతుంది"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"మీరు అధిక నాణ్యత గల సేవ్ చేసిన నెట్‌వర్క్‌కు సమీపంగా ఉన్నప్పుడు"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"తిరిగి ఆన్ చేయవద్దు"</string>
@@ -1679,7 +1673,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"యాక్సెస్ సామర్థ్య షార్ట్‌కట్ ద్వారా <xliff:g id="SERVICE_NAME">%1$s</xliff:g> ఆఫ్ చేయబడింది"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>ని ఉపయోగించడానికి వాల్యూమ్ కీలు రెండింటినీ 3 సెకన్లు నొక్కి ఉంచండి"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"యాక్సెసిబిలిటీ బటన్‌ను మీరు నొక్కినప్పుడు ఉపయోగించాల్సిన ఒక ఫీచర్‌ను ఎంచుకోండి:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (రెండు చేతి వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (రెండు వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (మూడు చేతి వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"సేవల మధ్య మారడానికి, యాక్సెసిబిలిటీ బటన్‌ను నొక్కి &amp; పట్టుకోండి."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"సేవల మధ్య మారడానికి, రెండు చేతి వేళ్ళతో పైకి స్వైప్ చేసి పట్టుకోండి."</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"వర్గీకరించబడలేదు"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"మీరు ఈ నోటిఫికేషన్‌ల ప్రాముఖ్యతను సెట్ చేసారు."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ఇందులో పేర్కొనబడిన వ్యక్తులను బట్టి ఇది చాలా ముఖ్యమైనది."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g>తో కొత్త వినియోగదారుని సృష్టించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించాలా ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g>తో (ఈ ఖాతాతో ఇప్పటికే ఒక వినియోగదారు ఉన్నారు) కొత్త వినియోగదారుని సృష్టించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించాలా?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"భాషను జోడించండి"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ప్రాంతం ప్రాధాన్యత"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"భాష పేరును టైప్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index e8c776c..f2376d7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -299,7 +299,7 @@
     <string name="permgrouplab_microphone" msgid="171539900250043464">"ไมโครโฟน"</string>
     <string name="permgroupdesc_microphone" msgid="4988812113943554584">"บันทึกเสียง"</string>
     <string name="permgrouprequest_microphone" msgid="9167492350681916038">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; บันทึกเสียงไหม"</string>
-    <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"กิจกรรมการเคลื่อนไหวร่างกาย"</string>
+    <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"การเคลื่อนไหวร่างกาย"</string>
     <string name="permgroupdesc_activityRecognition" msgid="6949472038320473478">"เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณ"</string>
     <string name="permgrouprequest_activityRecognition" msgid="7626438016904799383">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณไหม"</string>
     <string name="permgrouplab_camera" msgid="4820372495894586615">"กล้องถ่ายรูป"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"แตะเพื่อดูเครือข่ายทั้งหมด"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"เชื่อมต่อ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"เครือข่ายทั้งหมด"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"อนุญาตให้เชื่อมต่อเครือข่าย Wi-Fi ที่แนะนำไหม"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"เครือข่ายที่แนะนำโดย <xliff:g id="NAME">%s</xliff:g> และอุปกรณ์อาจเชื่อมต่อโดยอัตโนมัติ"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"อนุญาต"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ไม่เป็นไร"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi จะเปิดโดยอัตโนมัติ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"เมื่อคุณอยู่ใกล้เครือข่ายคุณภาพสูงที่บันทึกไว้"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ไม่ต้องเปิดอีกครั้ง"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ไม่จัดอยู่ในหมวดหมู่ใดๆ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"คุณตั้งค่าความสำคัญของการแจ้งเตือนเหล่านี้"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ข้อความนี้สำคัญเนื่องจากบุคคลที่เกี่ยวข้อง"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> สร้างผู้ใช้ใหม่ด้วย <xliff:g id="ACCOUNT">%2$s</xliff:g> ไหม"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> สร้างผู้ใช้ใหม่ด้วย <xliff:g id="ACCOUNT">%2$s</xliff:g> (มีผู้ใช้ที่มีบัญชีนี้อยู่แล้ว) ไหม"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"เพิ่มภาษา"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ค่ากำหนดภูมิภาค"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"พิมพ์ชื่อภาษา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index fc0a90c..9acd72a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"I-tap upang makita ang lahat ng network"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kumonekta"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Lahat ng network"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Payagan ang mga iminumungkahing Wi‑Fi network?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mga iminumungkahing network ng <xliff:g id="NAME">%s</xliff:g>. Posibleng awtomatikong kumonekta ang device."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Payagan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hindi, salamat na lang"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Awtomatikong mag-o-on ang Wi‑Fi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kapag malapit ka sa naka-save na network na mataas ang kalidad"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Huwag i-on muli"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Di-nakategorya"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ikaw ang magtatakda sa kahalagahan ng mga notification na ito."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Mahalaga ito dahil sa mga taong kasangkot."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na gumawa ng bagong User sa <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na gumawa ng bagong User sa <xliff:g id="ACCOUNT">%2$s</xliff:g> (mayroon nang User sa account na ito) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Magdagdag ng wika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Kagustuhan sa rehiyon"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"I-type ang wika"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5b61b97..e2bda9f 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tüm ağları görmek için dokunun"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Bağlan"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tüm ağlar"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Önerilen kablosuz ağlara izin verilsin mi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> tarafından önerilen ağlar. Cihaz otomatik olarak bağlanabilir."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"İzin ver"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hayır, teşekkürler"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kablosuz özelliği otomatik olarak açılacak"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Daha önce kaydedilmiş yüksek kaliteli bir ağın yakınında olduğunuzda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Tekrar açılmasın"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategorize edilmemiş"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bu bildirimlerin önem derecesini ayarladınız."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Bu, dahil olan kişiler nedeniyle önemlidir."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabına sahip yeni bir Kullanıcı eklemesine izin verilsin mi?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabına sahip yeni bir Kullanıcı eklemesine izin verilsin mi (bu hesaba sahip bir kullanıcı zaten var)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dil ekleyin"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Bölge tercihi"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Dil adını yazın"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 0855110..ee52575 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Торкніться, щоб побачити всі мережі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Під’єднатися"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Усі мережі"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Дозволити пропоновані мережі Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Мережі, пропоновані додатком <xliff:g id="NAME">%s</xliff:g>. Пристрій може підключитися автоматично."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволити"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ні, дякую"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi вмикатиметься автоматично"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Коли ви поблизу збереженої мережі високої якості"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вмикати знову"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Без категорії"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ви вказуєте пріоритет цих сповіщень."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Важливе з огляду на учасників."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> створити нового користувача з обліковим записом <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> створити нового користувача з обліковим записом <xliff:g id="ACCOUNT">%2$s</xliff:g> (користувач із таким обліковим записом уже існує)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додати мову"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Вибір регіону"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Введіть назву мови"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index f5978e2..5cc8db7 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"تمام نیٹ ورکس دیکھنے کیلئے تھپتھپائيں"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"منسلک کریں"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"سبھی نیٹ ورکس"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏تجویز کردہ Wi‑Fi نیٹ ورکس کو اجازت دیں؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> تجویز کردہ نیٹ ورکس۔ آلہ خودکار طور پر منسلک ہو سکتا ہے۔"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"اجازت ہیں"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"نہیں شکریہ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi از خود آن ہو جائے گا"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"جب آپ اعلی معیار کے محفوظ کردہ نیٹ ورک کے قریب ہوں"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوبارہ آن نہ کریں"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"غیر زمرہ بند"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ان اطلاعات کی اہمیت آپ مقرر کرتے ہیں۔"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"اس میں موجود لوگوں کی وجہ سے یہ اہم ہے۔"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> کو <xliff:g id="ACCOUNT">%2$s</xliff:g> کے ساتھ ایک نیا صارف بنانے کی اجازت دیں؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> کو <xliff:g id="ACCOUNT">%2$s</xliff:g> کے ساتھ ایک نیا صارف بنانے کی اجازت دیں (اس اکاؤنٹ کے ساتھ ایک صارف پہلے سے موجود ہے) ؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ایک زبان شامل کریں"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"علاقہ کی ترجیح"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"زبان کا نام ٹائپ کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 681e742..cb5191d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"rasm va videoga olish"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"Bu ilova xohlagan vaqtda kamera orqali suratga olishi va video yozib olishi mumkin."</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"Ilova yoki xizmatga tizim kamerasi orqali surat va videolar olishga ruxsat berish"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Bu imtiyozli | tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA permission ruxsati ham yoqilgan boʻlishi talab qilinadi"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Bu imtiyozli | tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA ruxsati ham yoqilgan boʻlishi talab qilinadi"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"tebranishni boshqarish"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Ilova tebranishli signallarni boshqarishi mumkin."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"telefon raqamlariga tog‘ridan to‘g‘ri qo‘ng‘iroq qilish"</string>
@@ -1227,12 +1227,12 @@
     <string name="volume_music_hint_silent_ringtone_selected" msgid="8310739960973156272">"Ovozsiz rejim tanlandi"</string>
     <string name="volume_call" msgid="3941680041282788711">"Suhbat vaqtidagi tovush balandligi"</string>
     <string name="volume_bluetooth_call" msgid="2002891926351151534">"Kiruvchi bluetooth tovushi"</string>
-    <string name="volume_alarm" msgid="1985191616042689100">"Signal tovushi balandligi"</string>
+    <string name="volume_alarm" msgid="1985191616042689100">"Signal tovushi"</string>
     <string name="volume_notification" msgid="2422265656744276715">"Eslatma tovushi"</string>
     <string name="volume_unknown" msgid="1400219669770445902">"Tovush balandligi"</string>
     <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Bluetooth tovushi"</string>
     <string name="volume_icon_description_ringer" msgid="3326003847006162496">"Rington balandligi"</string>
-    <string name="volume_icon_description_incall" msgid="8890073218154543397">"Qo‘ng‘iroq tovushi balandligi"</string>
+    <string name="volume_icon_description_incall" msgid="8890073218154543397">"Suhbat tovushi"</string>
     <string name="volume_icon_description_media" msgid="4217311719665194215">"Multimedia tovushi"</string>
     <string name="volume_icon_description_notification" msgid="7044986546477282274">"Eslatma tovushi"</string>
     <string name="ringtone_default" msgid="3789758980357696936">"Standart rington"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Barcha tarmoqlarni ko‘rish uchun bosing"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ulanish"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Barcha tarmoqlar"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Tavsiya qilingan Wi‑Fi tarmoqlarga ruxsat berilsinmi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> tavsiya qilgan tarmoqlar. Qurilma avtomatik ulanishi mumkin."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Ruxsat"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Kerak emas"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik ravishda yoqiladi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saqlangan tarmoqlar ichidan signali yaxshisi hududida ekaningizda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Qayta yoqilmasin"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Ulangan qurilma quvvatlanmoqda. Boshqa parametrlar uchun bosing."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Analogli audio uskuna aniqlandi"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Biriktirilgan qurilma mazkur telefon bilan mos emas. Batafsil axborot olish uchun bu yerga bosing."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB orqali nosozliklarni aniqlash"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"USB orqali nosozliklarni aniqlashni faolsizlantirish uchun bosing"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB orqali nosozliklarni tuzatishni o‘chirib qo‘yish uchun bosing."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Xavfsizlik sinovi rejimi yoqildi"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Turkumlanmagan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Siz ushbu bildirishnomalarning muhimligini belgilagansiz."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Bu odamlar siz uchun muhim."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga <xliff:g id="ACCOUNT">%2$s</xliff:g> hisobi bilan yangi foydalanuvchi yaratishiga ruxsat berilsinmi ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga <xliff:g id="ACCOUNT">%2$s</xliff:g> hisobi bilan yangi foydalanuvchi yaratishiga ruxsat berilsinmi (bunday hisobdagi foydalanuvchi allaqachon mavjud) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Til qoʻshish"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Hudud sozlamalari"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Til nomini kiriting"</string>
@@ -1935,7 +1933,7 @@
     <string name="app_category_maps" msgid="5878491404538024367">"Xaritalar va navigatsiya"</string>
     <string name="app_category_productivity" msgid="3742083261781538852">"Ish va unumdorlik"</string>
     <string name="device_storage_monitor_notification_channel" msgid="3295871267414816228">"Qurilma xotirasi"</string>
-    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB orqali nosozliklarni aniqlash"</string>
     <string name="time_picker_hour_label" msgid="2979075098868106450">"soat"</string>
     <string name="time_picker_minute_label" msgid="5168864173796598399">"daqiqa"</string>
     <string name="time_picker_header_text" msgid="143536825321922567">"Vaqtni sozlash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 9f8d48a..e41dc43 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Nhấn để xem tất cả các mạng"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kết nối"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tất cả các mạng"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Cho phép các mạng Wi‑Fi được đề xuất?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Các mạng do <xliff:g id="NAME">%s</xliff:g> đề xuất. Thiết bị có thể kết nối tự động."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Cho phép"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Không, cảm ơn"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sẽ tự động bật"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Khi bạn ở gần mạng đã lưu chất lượng cao"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Không bật lại"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Đã tắt phím tắt trợ năng <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Nhấn và giữ đồng thời cả hai phím âm lượng trong 3 giây để sử dụng <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Chọn dịch vụ sẽ sử dụng khi bạn nhấn vào nút hỗ trợ tiếp cận:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận (vuốt lên từ cuối màn hình bằng 2 ngón tay):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận (vuốt lên từ cuối màn hình bằng 3 ngón tay):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận này (vuốt lên từ cuối màn hình bằng 2 ngón tay):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận này (vuốt lên từ cuối màn hình bằng 3 ngón tay):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Để chuyển đổi giữa các dịch vụ, hãy chạm và giữ nút hỗ trợ tiếp cận."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Để chuyển đổi giữa các dịch vụ, hãy vuốt lên và giữ bằng 2 ngón tay."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Để chuyển đổi giữa các dịch vụ, hãy vuốt lên và giữ bằng 3 ngón tay."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Chưa được phân loại"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bạn đặt tầm quan trọng của các thông báo này."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Thông báo này quan trọng vì những người có liên quan."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Cho phép <xliff:g id="APP">%1$s</xliff:g> tạo người dùng mới bằng <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Cho phép <xliff:g id="APP">%1$s</xliff:g> tạo người dùng mới bằng <xliff:g id="ACCOUNT">%2$s</xliff:g> (người dùng có tài khoản này đã tồn tại)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Thêm ngôn ngữ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Tùy chọn khu vực"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nhập tên ngôn ngữ"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 0293237..ba39f1e2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问您的通讯录吗?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"位置信息"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"获取此设备的位置信息"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”获取此设备的位置信息吗?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"要允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”获取此设备的位置信息吗?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"只有当您使用该应用时,该应用才有权访问位置信息"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"要&lt;b&gt;一律允许&lt;/b&gt;允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问此设备的位置信息吗?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"目前只有当您使用该应用时,该应用才能访问位置信息"</string>
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问您的日历吗?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"短信"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"发送和查看短信"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;发送和查看短信吗?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"要允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;发送和查看短信吗?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"存储空间"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"访问您设备上的照片、媒体内容和文件"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;b&gt;&lt;/b&gt;访问您设备上的照片、媒体内容和文件吗?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"点按即可查看所有网络"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"连接"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有网络"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"是否允许系统连接到建议的 WLAN 网络?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>建议的网络。设备可能会自动连接到这些网络。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允许"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN 将自动开启"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"当您位于已保存的高品质网络信号范围内时"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新开启"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分类"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"这些通知的重要程度由您来设置。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"这条通知涉及特定的人,因此被归为重要通知。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"允许<xliff:g id="APP">%1$s</xliff:g>使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 创建新用户吗?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"允许<xliff:g id="APP">%1$s</xliff:g>使用 <xliff:g id="ACCOUNT">%2$s</xliff:g>(目前已有用户使用此帐号)创建新用户吗?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"添加语言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"区域偏好设置"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"输入语言名称"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 9c30e70..f0fce71 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"此應用程式可以隨時使用相機拍照和攝錄。"</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"允許應用程式或服務存取系統相機來拍照和攝錄"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"此特別權限 | 系統應用程式可以隨時使用系統相機來拍照和攝錄。應用程式亦需要獲得 Android 權限/相機權限"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"這個獲特別權限的系統應用程式可以在任何時候使用系統相機來拍照和攝錄。此外,應用程式亦需要 android.permission.CAMERA 權限"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"控制震動"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"允許應用程式控制震動。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"直接撥打電話號碼"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕按即可查看所有網絡"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有網絡"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"要允許連線至建議的 Wi-Fi 網絡嗎?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"「<xliff:g id="NAME">%s</xliff:g>」已建議網絡連線,裝置可能會自動連接網絡。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允許"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了,謝謝"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi 將會自動開啟"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當您位於已儲存的高品質網絡信號範圍內時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分類"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"您可以設定這些通知的重要性。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"列為重要的原因:涉及的人。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"要允許 <xliff:g id="APP">%1$s</xliff:g> 使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"要允許 <xliff:g id="APP">%1$s</xliff:g> 使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者 (此帳戶目前已有此使用者) 嗎?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"新增語言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地區偏好設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"輸入語言名稱"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index ce95582..1f13208 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕觸即可查看所有網路"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有網路"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"是否允許系統連線到建議的 Wi‑Fi 網路?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"「<xliff:g id="NAME">%s</xliff:g>」建議的網路。裝置可能會自動連線到這些網路。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允許"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了,謝謝"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi 將自動開啟"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當你位於已儲存的高品質網路範圍內時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分類"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"這些通知的重要性由你決定。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"這則通知涉及特定人士,因此被歸為重要通知。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"要允許 <xliff:g id="APP">%1$s</xliff:g> 為 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"要允許 <xliff:g id="APP">%1$s</xliff:g> 為 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎 (這個帳戶目前已有使用者)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"新增語言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地區偏好設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"請輸入語言名稱"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index e5e5311..f707fcc 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Thepha ukuze ubone onke amanethiwekhi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Xhuma"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Onke amanethiwekhi"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vumela amanethiwekhi e-Wi-Fi aphakanyisiwe?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> amanethiwekhi aphakanyisiwe. Idivayisi ingaxhumeka ngokuzenzakalela."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Vumela"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Cha ngiyabonga"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"I-Wi-Fi izovuleka ngokuzenzakalela"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Uma useduze kwenethiwekhi yekhwalithi ephezulu elondoloziwe"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ungaphindi uvule"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Akufakwanga esigabeni"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Usethe ukubaluleka kwalezi zaziso."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Lokhu kubalulekile ngenxa yabantu ababandakanyekayo."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukudala umsebenzisi omusha nge-<xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukudala umsebenzisi omusha nge-<xliff:g id="ACCOUNT">%2$s</xliff:g> (umsebenzisi onale akhawunti usuvel ukhona) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Engeza ulwimi"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Okuncamelayo kwesifunda"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Thayipha igama lolimi"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2cd39cb..5fd53de 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4202,6 +4202,10 @@
         -->
     </integer-array>
 
+    <!-- Default refresh rate in the zone defined by brightness and ambient thresholds.
+         If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+    <integer name="config_defaultRefreshRateInZone">0</integer>
+
     <!-- The type of the light sensor to be used by the display framework for things like
          auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
     <string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 18d7706..56e1fed 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1745,6 +1745,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_startViewPermissionUsage">Allows the holder to start the permission usage for an app. Should never be needed for normal apps.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_accessibilityShortcutTarget">accessibility shortcut target</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_accessibilityShortcutTarget">Allows an app to define the accessibility shortcut target.</string>
+
     <!-- Policy administration -->
 
     <!-- Title of policy access to limiting the user's password choices -->
@@ -5015,9 +5020,9 @@
     <string name="importance_from_person">This is important because of the people involved.</string>
 
     <!-- Message to user that app trying to create user for an account that already exists. [CHAR LIMIT=none] -->
-    <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> ?</string>
+    <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">%2$s</xliff:g> (a User with this account already exists) ?</string>
+    <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>
 
     <!-- Locale picker strings -->
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eaebd91..761e02f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -206,6 +206,8 @@
   <java-symbol type="id" name="action3" />
   <java-symbol type="id" name="action4" />
   <java-symbol type="id" name="media_seamless" />
+  <java-symbol type="id" name="media_seamless_image" />
+  <java-symbol type="id" name="media_seamless_text" />
   <java-symbol type="id" name="notification_media_seekbar_container" />
   <java-symbol type="id" name="notification_media_content" />
   <java-symbol type="id" name="notification_media_progress" />
@@ -3801,6 +3803,7 @@
 
   <!-- For high refresh rate displays -->
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
+  <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
 
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index a92c500..f71c8b0 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -70,7 +70,7 @@
     <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
 
     <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
-    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075" />
+    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" />
 
     <!-- Chile: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="cl" pattern="\\d{4,5}" free="9963|9240" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1670d49..a4c504b 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,7 +43,6 @@
 
     libs: [
         "android.test.runner",
-        "telephony-common",
         "testables",
         "org.apache.http.legacy",
         "android.test.base",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 9955c51..2417209 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1270,10 +1270,8 @@
                 <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED" />
             </intent-filter>
         </receiver>
-        <service android:name="android.app.activity.LocalService">
-            <intent-filter>
-                <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL" />
-            </intent-filter>
+        <service android:name="android.app.activity.ServiceTest$RemoteService"
+                android:process=":RemoteService">
             <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
             <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
             <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
@@ -1281,18 +1279,6 @@
             <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
             <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
         </service>
-        <service android:name="android.app.activity.LocalDeniedService"
-                android:permission="com.android.frameworks.coretests.permission.TEST_DENIED">
-            <intent-filter>
-                <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED" />
-            </intent-filter>
-        </service>
-        <service android:name="android.app.activity.LocalGrantedService"
-                android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED">
-            <intent-filter>
-                <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED" />
-            </intent-filter>
-        </service>
 
         <service
             android:name="android.service.settings.suggestions.MockSuggestionService"
diff --git a/core/tests/coretests/src/android/app/activity/LocalDeniedService.java b/core/tests/coretests/src/android/app/activity/LocalDeniedService.java
deleted file mode 100644
index 3bdac22..0000000
--- a/core/tests/coretests/src/android/app/activity/LocalDeniedService.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.activity;
-
-public class LocalDeniedService extends LocalService
-{
-}
-
diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java b/core/tests/coretests/src/android/app/activity/LocalGrantedService.java
deleted file mode 100644
index 7ab0fb4..0000000
--- a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.activity;
-
-public class LocalGrantedService extends LocalService
-{
-}
-
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index bfd543f..7f81339 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -23,9 +23,9 @@
 import android.content.IntentFilter;
 import android.content.ReceiverCallNotAllowedException;
 import android.content.ServiceConnection;
-import android.os.RemoteException;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.RemoteException;
 
 public class LocalReceiver extends BroadcastReceiver {
     public LocalReceiver() {
@@ -52,7 +52,7 @@
                     public void onServiceDisconnected(ComponentName name) {
                     }
                 };
-                context.bindService(new Intent(context, LocalService.class), sc, 0);
+                context.bindService(new Intent(context, ServiceTest.RemoteService.class), sc, 0);
                 context.unbindService(sc);
             } catch (ReceiverCallNotAllowedException e) {
                 //resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/LocalService.java b/core/tests/coretests/src/android/app/activity/LocalService.java
deleted file mode 100644
index c31ca4b..0000000
--- a/core/tests/coretests/src/android/app/activity/LocalService.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.activity;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.util.Log;
-
-public class LocalService extends Service {
-    private final IBinder mBinder = new Binder() {
-
-        @Override
-        protected boolean onTransact(int code, Parcel data, Parcel reply,
-                int flags) throws RemoteException {
-            if (code == ServiceTest.SET_REPORTER_CODE) {
-                data.enforceInterface(ServiceTest.SERVICE_LOCAL);
-                mReportObject = data.readStrongBinder();
-                return true;
-            } else {
-                return super.onTransact(code, data, reply, flags);
-            }
-        }
-        
-    };
-
-    private IBinder mReportObject;
-    private int mStartCount = 1;
-
-    public LocalService() {
-    }
-
-    @Override
-    public void onStart(Intent intent, int startId) {
-        //Log.i("LocalService", "onStart: " + intent);
-        if (intent.getExtras() != null) {
-            mReportObject = intent.getExtras().getIBinder(ServiceTest.REPORT_OBJ_NAME);
-            if (mReportObject != null) {
-                try {
-                    Parcel data = Parcel.obtain();
-                    data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
-                    data.writeInt(mStartCount);
-                    mStartCount++;
-                    mReportObject.transact(
-                            ServiceTest.STARTED_CODE, data, null, 0);
-                    data.recycle();
-                } catch (RemoteException e) {
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.i("LocalService", "onDestroy: mReportObject=" + mReportObject);
-        if (mReportObject != null) {
-            try {
-                Parcel data = Parcel.obtain();
-                data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
-                mReportObject.transact(
-                        ServiceTest.DESTROYED_CODE, data, null, 0);
-                data.recycle();
-            } catch (RemoteException e) {
-            }
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.i("LocalService", "onBind: " + intent);
-        return mBinder;
-    }
-    
-    @Override
-    public boolean onUnbind(Intent intent) {
-        Log.i("LocalService", "onUnbind: " + intent);
-        if (mReportObject != null) {
-            try {
-                Parcel data = Parcel.obtain();
-                data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
-                mReportObject.transact(
-                        ServiceTest.UNBIND_CODE, data, null, 0);
-                data.recycle();
-            } catch (RemoteException e) {
-            }
-        }
-        return true;
-    }
-    
-    @Override
-    public void onRebind(Intent intent) {
-        Log.i("LocalService", "onUnbind: " + intent);
-        if (mReportObject != null) {
-            try {
-                Parcel data = Parcel.obtain();
-                data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
-                mReportObject.transact(
-                        ServiceTest.REBIND_CODE, data, null, 0);
-                data.recycle();
-            } catch (RemoteException e) {
-            }
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/app/activity/MetaDataTest.java b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
index cf27878..be6e276 100644
--- a/core/tests/coretests/src/android/app/activity/MetaDataTest.java
+++ b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
@@ -125,7 +125,7 @@
 
     @SmallTest
     public void testServiceWithData() throws Exception {
-        ComponentName cn = new ComponentName(mContext, LocalService.class);
+        ComponentName cn = new ComponentName(mContext, ServiceTest.RemoteService.class);
         ServiceInfo si = mContext.getPackageManager().getServiceInfo(
                 cn, PackageManager.GET_META_DATA);
 
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index 9d2aebd..c89f37d 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -16,453 +16,250 @@
 
 package android.app.activity;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.Service;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.os.Binder;
-import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 
 import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
 
-// These test binders purport to support an interface whose canonical
-// interface name is ServiceTest.SERVICE_LOCAL
-// Temporarily suppress, this test is causing unit test suite run to fail
-// TODO: remove this suppress
-@Suppress
-public class ServiceTest extends ActivityTestsBase {
+import junit.framework.TestCase;
 
-    public static final String SERVICE_LOCAL =
-            "com.android.frameworks.coretests.activity.SERVICE_LOCAL";
-    public static final String SERVICE_LOCAL_GRANTED =
-            "com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED";
-    public static final String SERVICE_LOCAL_DENIED =
-            "com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED";
+import org.junit.Test;
 
-    public static final String REPORT_OBJ_NAME = "report";
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
 
-    public static final int STARTED_CODE = 1;
-    public static final int DESTROYED_CODE = 2;
-    public static final int SET_REPORTER_CODE = 3;
-    public static final int UNBIND_CODE = 4;
-    public static final int REBIND_CODE = 5;
+/**
+ * Test for verifying the behavior of {@link Service}.
+ * <p>
+ * Tests related to internal behavior are usually placed here, e.g. the restart delay may be
+ * different depending on the current amount of restarting services.
+ * <p>
+ * Build/Install/Run:
+ *  atest FrameworksCoreTests:ServiceTest
+ */
+@MediumTest
+public class ServiceTest extends TestCase {
+    private static final String ACTION_SERVICE_STARTED = RemoteService.class.getName() + "_STARTED";
+    private static final String EXTRA_START_CODE = "start_code";
+    private static final String EXTRA_PID = "pid";
 
-    public static final int STATE_START_1 = 0;
-    public static final int STATE_START_2 = 1;
-    public static final int STATE_UNBIND = 2;
-    public static final int STATE_DESTROY = 3;
-    public static final int STATE_REBIND = 4;
-    public static final int STATE_UNBIND_ONLY = 5;
-    public int mStartState;
+    private static final long TIMEOUT_SEC = 5;
+    private static final int NOT_STARTED = -1;
 
-    public IBinder mStartReceiver = new Binder() {
+    private final Context mContext = getInstrumentation().getContext();
+    private final Intent mServiceIntent = new Intent(mContext, RemoteService.class);
+    private TestConnection mCurrentConnection;
+
+    @Override
+    public void tearDown() {
+        mContext.stopService(mServiceIntent);
+        if (mCurrentConnection != null) {
+            mContext.unbindService(mCurrentConnection);
+            mCurrentConnection = null;
+        }
+    }
+
+    @Test
+    public void testRestart_stickyStartedService_restarted() {
+        testRestartStartedService(Service.START_STICKY, true /* shouldRestart */);
+    }
+
+    @Test
+    public void testRestart_redeliveryStartedService_restarted() {
+        testRestartStartedService(Service.START_FLAG_REDELIVERY, true /* shouldRestart */);
+    }
+
+    @Test
+    public void testRestart_notStickyStartedService_notRestarted() {
+        testRestartStartedService(Service.START_NOT_STICKY, false /* shouldRestart */);
+    }
+
+    private void testRestartStartedService(int startFlag, boolean shouldRestart) {
+        final int servicePid = startService(startFlag);
+        assertThat(servicePid, not(NOT_STARTED));
+
+        final int restartedServicePid = waitForServiceStarted(
+                () -> Process.killProcess(servicePid));
+        assertThat(restartedServicePid, shouldRestart ? not(NOT_STARTED) : is(NOT_STARTED));
+    }
+
+    @Test
+    public void testRestart_boundService_restarted() {
+        final int servicePid = bindService(Context.BIND_AUTO_CREATE);
+        assertThat(servicePid, not(NOT_STARTED));
+
+        Process.killProcess(servicePid);
+        // The service should be restarted and the connection will receive onServiceConnected again.
+        assertThat(mCurrentConnection.takePid(), not(NOT_STARTED));
+    }
+
+    @Test
+    public void testRestart_boundNotStickyStartedService_restarted() {
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        final Supplier<RunningServiceInfo> serviceInfoGetter = () -> {
+            for (RunningServiceInfo rs : am.getRunningServices(Integer.MAX_VALUE)) {
+                if (mServiceIntent.getComponent().equals(rs.service)) {
+                    return rs;
+                }
+            }
+            return null;
+        };
+
+        final int servicePid = bindService(Context.BIND_AUTO_CREATE);
+        assertThat(servicePid, not(NOT_STARTED));
+        assertThat(startService(Service.START_NOT_STICKY), is(servicePid));
+
+        RunningServiceInfo info = serviceInfoGetter.get();
+        assertThat(info, notNullValue());
+        assertThat(info.started, is(true));
+
+        Process.killProcess(servicePid);
+        // The service will be restarted for connection but the started state should be gone.
+        final int restartedServicePid = mCurrentConnection.takePid();
+        assertThat(restartedServicePid, not(NOT_STARTED));
+
+        info = serviceInfoGetter.get();
+        assertThat(info, notNullValue());
+        assertThat(info.started, is(false));
+        assertThat(info.clientCount, is(1));
+    }
+
+    @Test
+    public void testRestart_notStickyStartedNoAutoCreateBoundService_notRestarted() {
+        final int servicePid = startService(Service.START_NOT_STICKY);
+        assertThat(servicePid, not(NOT_STARTED));
+        assertThat(bindService(0 /* flags */), is(servicePid));
+
+        Process.killProcess(servicePid);
+        assertThat(mCurrentConnection.takePid(), is(NOT_STARTED));
+    }
+
+    /** @return The pid of the started service. */
+    private int startService(int code) {
+        return waitForServiceStarted(
+                () -> mContext.startService(mServiceIntent.putExtra(EXTRA_START_CODE, code)));
+    }
+
+    /** @return The pid of the started service. */
+    private int waitForServiceStarted(Runnable serviceTrigger) {
+        final CompletableFuture<Integer> pidResult = new CompletableFuture<>();
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
+                mContext.unregisterReceiver(this);
+            }
+        }, new IntentFilter(ACTION_SERVICE_STARTED));
+
+        serviceTrigger.run();
+        try {
+            return pidResult.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+        } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
+        }
+        return NOT_STARTED;
+    }
+
+    /** @return The pid of the bound service. */
+    private int bindService(int flags) {
+        mCurrentConnection = new TestConnection();
+        assertThat(mContext.bindService(mServiceIntent, mCurrentConnection, flags), is(true));
+        return mCurrentConnection.takePid();
+    }
+
+    private static class TestConnection implements ServiceConnection {
+        private CompletableFuture<Integer> mServicePid = new CompletableFuture<>();
+
+        /**
+         * @return The pid of the connected service. It is only valid once after
+         *         {@link #onServiceConnected} is called.
+         */
+        int takePid() {
+            try {
+                return mServicePid.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
+            } finally {
+                mServicePid = new CompletableFuture<>();
+            }
+            return NOT_STARTED;
+        }
+
         @Override
-        protected boolean onTransact(int code, Parcel data, Parcel reply,
-                int flags) throws RemoteException {
-            //Log.i("ServiceTest", "Received code " + code + " in state " + mStartState);
-            if (code == STARTED_CODE) {
-                data.enforceInterface(SERVICE_LOCAL);
-                int count = data.readInt();
-                if (mStartState == STATE_START_1) {
-                    if (count == 1) {
-                        finishGood();
-                    } else {
-                        finishBad("onStart() again on an object when it should have been the first time");
-                    }
-                } else if (mStartState == STATE_START_2) {
-                    if (count == 2) {
-                        finishGood();
-                    } else {
-                        finishBad("onStart() the first time on an object when it should have been the second time");
-                    }
-                } else {
-                    finishBad("onStart() was called when not expected (state="+mStartState+")");
-                }
-                return true;
-            } else if (code == DESTROYED_CODE) {
-                data.enforceInterface(SERVICE_LOCAL);
-                if (mStartState == STATE_DESTROY) {
-                    finishGood();
-                } else {
-                    finishBad("onDestroy() was called when not expected (state="+mStartState+")");
-                }
-                return true;
-            } else if (code == UNBIND_CODE) {
-                data.enforceInterface(SERVICE_LOCAL);
-                if (mStartState == STATE_UNBIND) {
-                    mStartState = STATE_DESTROY;
-                } else if (mStartState == STATE_UNBIND_ONLY) {
-                    finishGood();
-                } else {
-                    finishBad("onUnbind() was called when not expected (state="+mStartState+")");
-                }
-                return true;
-            } else if (code == REBIND_CODE) {
-                data.enforceInterface(SERVICE_LOCAL);
-                if (mStartState == STATE_REBIND) {
-                    finishGood();
-                } else {
-                    finishBad("onRebind() was called when not expected (state="+mStartState+")");
-                }
-                return true;
-            } else {
-                return super.onTransact(code, data, reply, flags);
-            }
-        }
-    };
-
-    public class EmptyConnection implements ServiceConnection {
         public void onServiceConnected(ComponentName name, IBinder service) {
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-        }
-    }
-
-    public class TestConnection implements ServiceConnection {
-        private final boolean mExpectDisconnect;
-        private final boolean mSetReporter;
-        private boolean mMonitor;
-        private int mCount;
-
-        public TestConnection(boolean expectDisconnect, boolean setReporter) {
-            mExpectDisconnect = expectDisconnect;
-            mSetReporter = setReporter;
-            mMonitor = !setReporter;
-        }
-
-        void setMonitor(boolean v) {
-            mMonitor = v;
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (mSetReporter) {
-                Parcel data = Parcel.obtain();
-                data.writeInterfaceToken(SERVICE_LOCAL);
-                data.writeStrongBinder(mStartReceiver);
-                try {
-                    service.transact(SET_REPORTER_CODE, data, null, 0);
-                } catch (RemoteException e) {
-                    finishBad("DeadObjectException when sending reporting object");
-                }
+            final Parcel data = Parcel.obtain();
+            final Parcel reply = Parcel.obtain();
+            data.writeInterfaceToken(RemoteService.DESCRIPTOR);
+            try {
+                service.transact(RemoteService.TRANSACTION_GET_PID, data, reply, 0 /* flags */);
+                reply.readException();
+                mServicePid.complete(reply.readInt());
+            } catch (RemoteException e) {
+                mServicePid.complete(NOT_STARTED);
+            } finally {
                 data.recycle();
-            }
-
-            if (mMonitor) {
-                mCount++;
-                if (mStartState == STATE_START_1) {
-                    if (mCount == 1) {
-                        finishGood();
-                    } else {
-                        finishBad("onServiceConnected() again on an object when it should have been the first time");
-                    }
-                } else if (mStartState == STATE_START_2) {
-                    if (mCount == 2) {
-                        finishGood();
-                    } else {
-                        finishBad("onServiceConnected() the first time on an object when it should have been the second time");
-                    }
-                } else {
-                    finishBad("onServiceConnected() called unexpectedly");
-                }
+                reply.recycle();
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
-            if (mMonitor) {
-                if (mStartState == STATE_DESTROY) {
-                    if (mExpectDisconnect) {
-                        finishGood();
-                    } else {
-                        finishBad("onServiceDisconnected() when it shouldn't have been");
+        }
+    }
+
+    public static class RemoteService extends Service {
+        static final String DESCRIPTOR = RemoteService.class.getName();
+        static final int TRANSACTION_GET_PID = Binder.FIRST_CALL_TRANSACTION;
+
+        @Override
+        public int onStartCommand(Intent intent, int flags, int startId) {
+            new Handler().post(() -> {
+                final Intent responseIntent = new Intent(ACTION_SERVICE_STARTED);
+                responseIntent.putExtra(EXTRA_PID, Process.myPid());
+                sendBroadcast(responseIntent);
+            });
+            if (intent != null && intent.hasExtra(EXTRA_START_CODE)) {
+                return intent.getIntExtra(EXTRA_START_CODE, Service.START_NOT_STICKY);
+            }
+            return super.onStartCommand(intent, flags, startId);
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return new Binder() {
+                @Override
+                protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                        throws RemoteException {
+                    if (code == TRANSACTION_GET_PID) {
+                        data.enforceInterface(DESCRIPTOR);
+                        reply.writeNoException();
+                        reply.writeInt(Process.myPid());
+                        return true;
                     }
-                } else {
-                    finishBad("onServiceDisconnected() called unexpectedly");
+                    return super.onTransact(code, data, reply, flags);
                 }
-            }
-        }
-    }
-
-    void startExpectResult(Intent service) {
-        startExpectResult(service, new Bundle());
-    }
-
-    void startExpectResult(Intent service, Bundle bundle) {
-        bundle.putIBinder(REPORT_OBJ_NAME, mStartReceiver);
-        boolean success = false;
-        try {
-            //Log.i("foo", "STATE_START_1");
-            mStartState = STATE_START_1;
-            getContext().startService(new Intent(service).putExtras(bundle));
-            waitForResultOrThrow(5 * 1000, "service to start first time");
-            //Log.i("foo", "STATE_START_2");
-            mStartState = STATE_START_2;
-            getContext().startService(new Intent(service).putExtras(bundle));
-            waitForResultOrThrow(5 * 1000, "service to start second time");
-            success = true;
-        } finally {
-            if (!success) {
-                try {
-                    getContext().stopService(service);
-                } catch (Exception e) {
-                    // eat
-                }
-            }
-        }
-        //Log.i("foo", "STATE_DESTROY");
-        mStartState = STATE_DESTROY;
-        getContext().stopService(service);
-        waitForResultOrThrow(5 * 1000, "service to be destroyed");
-    }
-
-    void startExpectNoPermission(Intent service) {
-        try {
-            getContext().startService(service);
-            fail("Expected security exception when starting " + service);
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    void bindExpectResult(Intent service) {
-        TestConnection conn = new TestConnection(true, false);
-        TestConnection conn2 = new TestConnection(false, false);
-        boolean success = false;
-        try {
-            // Expect to see the TestConnection connected.
-            mStartState = STATE_START_1;
-            getContext().bindService(service, conn, 0);
-            getContext().startService(service);
-            waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
-            // Expect to see the second TestConnection connected.
-            getContext().bindService(service, conn2, 0);
-            waitForResultOrThrow(5 * 1000, "new connection to receive service");
-
-            getContext().unbindService(conn2);
-            success = true;
-        } finally {
-            if (!success) {
-                try {
-                    getContext().stopService(service);
-                    getContext().unbindService(conn);
-                    getContext().unbindService(conn2);
-                } catch (Exception e) {
-                    // eat
-                }
-            }
-        }
-
-        // Expect to see the TestConnection disconnected.
-        mStartState = STATE_DESTROY;
-        getContext().stopService(service);
-        waitForResultOrThrow(5 * 1000, "existing connection to lose service");
-
-        getContext().unbindService(conn);
-
-        conn = new TestConnection(true, true);
-        success = false;
-        try {
-            // Expect to see the TestConnection connected.
-            conn.setMonitor(true);
-            mStartState = STATE_START_1;
-            getContext().bindService(service, conn, 0);
-            getContext().startService(service);
-            waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
-            success = true;
-        } finally {
-            if (!success) {
-                try {
-                    getContext().stopService(service);
-                    getContext().unbindService(conn);
-                } catch (Exception e) {
-                    // eat
-                }
-            }
-        }
-
-        // Expect to see the service unbind and then destroyed.
-        conn.setMonitor(false);
-        mStartState = STATE_UNBIND;
-        getContext().stopService(service);
-        waitForResultOrThrow(5 * 1000, "existing connection to lose service");
-
-        getContext().unbindService(conn);
-
-        conn = new TestConnection(true, true);
-        success = false;
-        try {
-            // Expect to see the TestConnection connected.
-            conn.setMonitor(true);
-            mStartState = STATE_START_1;
-            getContext().bindService(service, conn, 0);
-            getContext().startService(service);
-            waitForResultOrThrow(5 * 1000, "existing connection to receive service");
-
-            success = true;
-        } finally {
-            if (!success) {
-                try {
-                    getContext().stopService(service);
-                    getContext().unbindService(conn);
-                } catch (Exception e) {
-                    // eat
-                }
-            }
-        }
-
-        // Expect to see the service unbind but not destroyed.
-        conn.setMonitor(false);
-        mStartState = STATE_UNBIND_ONLY;
-        getContext().unbindService(conn);
-        waitForResultOrThrow(5 * 1000, "existing connection to unbind service");
-
-        // Expect to see the service rebound.
-        mStartState = STATE_REBIND;
-        getContext().bindService(service, conn, 0);
-        waitForResultOrThrow(5 * 1000, "existing connection to rebind service");
-
-        // Expect to see the service unbind and then destroyed.
-        mStartState = STATE_UNBIND;
-        getContext().stopService(service);
-        waitForResultOrThrow(5 * 1000, "existing connection to lose service");
-
-        getContext().unbindService(conn);
-    }
-
-    void bindAutoExpectResult(Intent service) {
-        TestConnection conn = new TestConnection(false, true);
-        boolean success = false;
-        try {
-            conn.setMonitor(true);
-            mStartState = STATE_START_1;
-            getContext().bindService(
-                    service, conn, Context.BIND_AUTO_CREATE);
-            waitForResultOrThrow(5 * 1000, "connection to start and receive service");
-            success = true;
-        } finally {
-            if (!success) {
-                try {
-                    getContext().unbindService(conn);
-                } catch (Exception e) {
-                    // eat
-                }
-            }
-        }
-        mStartState = STATE_UNBIND;
-        getContext().unbindService(conn);
-        waitForResultOrThrow(5 * 1000, "disconnecting from service");
-    }
-
-    void bindExpectNoPermission(Intent service) {
-        TestConnection conn = new TestConnection(false, false);
-        try {
-            getContext().bindService(service, conn, Context.BIND_AUTO_CREATE);
-            fail("Expected security exception when binding " + service);
-        } catch (SecurityException e) {
-            // expected
-        } finally {
-            getContext().unbindService(conn);
-        }
-    }
-
-
-    @MediumTest
-    public void testLocalStartClass() throws Exception {
-        startExpectResult(new Intent(getContext(), LocalService.class));
-    }
-
-    @MediumTest
-    public void testLocalStartAction() throws Exception {
-        startExpectResult(new Intent(SERVICE_LOCAL));
-    }
-
-    @MediumTest
-    public void testLocalBindClass() throws Exception {
-        bindExpectResult(new Intent(getContext(), LocalService.class));
-    }
-
-    @MediumTest
-    public void testLocalBindAction() throws Exception {
-        bindExpectResult(new Intent(SERVICE_LOCAL));
-    }
-
-    @MediumTest
-    public void testLocalBindAutoClass() throws Exception {
-        bindAutoExpectResult(new Intent(getContext(), LocalService.class));
-    }
-
-    @MediumTest
-    public void testLocalBindAutoAction() throws Exception {
-        bindAutoExpectResult(new Intent(SERVICE_LOCAL));
-    }
-
-    @MediumTest
-    public void testLocalStartClassPermissionGranted() throws Exception {
-        startExpectResult(new Intent(getContext(), LocalGrantedService.class));
-    }
-
-    @MediumTest
-    public void testLocalStartActionPermissionGranted() throws Exception {
-        startExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
-    }
-
-    @MediumTest
-    public void testLocalBindClassPermissionGranted() throws Exception {
-        bindExpectResult(new Intent(getContext(), LocalGrantedService.class));
-    }
-
-    @MediumTest
-    public void testLocalBindActionPermissionGranted() throws Exception {
-        bindExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
-    }
-
-    @MediumTest
-    public void testLocalBindAutoClassPermissionGranted() throws Exception {
-        bindAutoExpectResult(new Intent(getContext(), LocalGrantedService.class));
-    }
-
-    @MediumTest
-    public void testLocalBindAutoActionPermissionGranted() throws Exception {
-        bindAutoExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
-    }
-
-    @MediumTest
-    public void testLocalStartClassPermissionDenied() throws Exception {
-        startExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
-    }
-
-    @MediumTest
-    public void testLocalStartActionPermissionDenied() throws Exception {
-        startExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
-    }
-
-    @MediumTest
-    public void testLocalBindClassPermissionDenied() throws Exception {
-        bindExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
-    }
-
-    @MediumTest
-    public void testLocalBindActionPermissionDenied() throws Exception {
-        bindExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
-    }
-
-    @MediumTest
-    public void testLocalUnbindTwice() throws Exception {
-        EmptyConnection conn = new EmptyConnection();
-        getContext().bindService(
-                new Intent(SERVICE_LOCAL_GRANTED), conn, 0);
-        getContext().unbindService(conn);
-        try {
-            getContext().unbindService(conn);
-            fail("No exception thrown on second unbind");
-        } catch (IllegalArgumentException e) {
-            //Log.i("foo", "Unbind exception", e);
+            };
         }
     }
 }
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 1633e1a..6ec3dc9 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -34,7 +34,10 @@
 
 import android.app.usage.UsageEvents.Event;
 import android.os.Parcel;
+import android.os.UserHandle;
+import android.support.test.uiautomator.UiDevice;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -121,6 +124,15 @@
     }
 
     @Test
+    public void testDataIsInCe() throws Exception {
+        final int userId = UserHandle.myUserId();
+        final String expectedPath = "/data/system_ce/" + userId + "/usagestats";
+        final String actualPath = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                .executeShellCommand("dumpsys usagestats stats-directory " + userId).trim();
+        assertEquals(expectedPath, actualPath);
+    }
+
+    @Test
     public void testParcelable() {
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
diff --git a/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java b/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
new file mode 100644
index 0000000..8ab9ddb
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.PackageBuilder.builder;
+import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_BASE;
+import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_MANAGER;
+import static android.content.pm.SharedLibraryNames.ANDROID_TELEPHONY_COMMON;
+
+import android.os.Build;
+import androidx.test.filters.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link AndroidHidlUpdater}
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class AndroidTelephonyCommonUpdaterTest extends PackageSharedLibraryUpdaterTest {
+
+    private static final String OTHER_LIBRARY = "other.library";
+    private static final String PHONE_UID = "android.uid.phone";
+
+    @Test
+    public void targeted_at_Q() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q);
+
+        PackageBuilder after = builder().targetSdkVersion(Build.VERSION_CODES.Q)
+            .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // Should add telephony-common libraries
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_phoneUID() {
+        PackageBuilder before = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q);
+
+        // Should add telephony-common libraries
+        PackageBuilder after = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_not_empty_usesLibraries() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(OTHER_LIBRARY);
+
+        // no change
+        checkBackwardsCompatibility(before, before);
+    }
+
+    @Test
+    public void targeted_at_Q_not_empty_usesLibraries_phoneUID() {
+        PackageBuilder before = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(OTHER_LIBRARY);
+
+        // The telephony-common jars should be added at the start of the list because it
+        // is not on the bootclasspath and the package targets pre-R.
+        PackageBuilder after = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON, OTHER_LIBRARY);
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_R_in_usesLibraries() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q + 1)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        PackageBuilder after = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // Libraries are removed because they are not available for apps target >= R and not run
+        // on phone-uid
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_in_usesLibraries() {
+        PackageBuilder before = builder().asSystemApp()
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // No change is required because the package explicitly requests the telephony libraries
+        // and is targeted at the current version so does not need backwards compatibility.
+        checkBackwardsCompatibility(before, before);
+    }
+
+
+    @Test
+    public void targeted_at_R_in_usesOptionalLibraries() {
+        PackageBuilder before = builder().targetSdkVersion(Build.VERSION_CODES.Q + 1)
+            .optionalLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // Dependency is removed, it is not available.
+        PackageBuilder after = builder().targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // Libraries are removed because they are not available for apps targeting Q+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_R() {
+        PackageBuilder before = builder()
+            .targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // no change
+        checkBackwardsCompatibility(before, before);
+    }
+
+    private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) {
+        checkBackwardsCompatibility(before, after, AndroidTelephonyCommonUpdater::new);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageBuilder.java b/core/tests/coretests/src/android/content/pm/PackageBuilder.java
index f7544af..f3a56e2 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBuilder.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBuilder.java
@@ -37,6 +37,8 @@
 
     private ArrayList<String> mOptionalLibraries;
 
+    private String mSharedUid;
+
     public static PackageBuilder builder() {
         return new PackageBuilder();
     }
@@ -47,6 +49,7 @@
         pkg.applicationInfo.flags = mFlags;
         pkg.usesLibraries = mRequiredLibraries;
         pkg.usesOptionalLibraries = mOptionalLibraries;
+        pkg.mSharedUserId = mSharedUid;
         return pkg;
     }
 
@@ -55,6 +58,11 @@
         return this;
     }
 
+    PackageBuilder setSharedUid(String uid) {
+        this.mSharedUid = uid;
+        return this;
+    }
+
     PackageBuilder asSystemApp() {
         this.mFlags |= ApplicationInfo.FLAG_SYSTEM;
         return this;
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 2fc3e36..4a93f42 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -16,16 +16,29 @@
 
 package android.content.res;
 
+import android.content.Context;
+import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.usage.IntervalStatsProto;
+
 import junit.framework.TestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Locale;
+
 /**
  * Build/install/run: bit FrameworksCoreTests:android.content.res.ConfigurationTest
  */
@@ -54,4 +67,70 @@
         config2.updateFrom(config);
         assertEquals(config2.screenLayout, Configuration.SCREENLAYOUT_COMPAT_NEEDED);
     }
+
+    @Test
+    public void testReadWriteProto() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final File testDir = new File(context.getFilesDir(), "ConfigurationTest");
+        testDir.mkdirs();
+        final File proto = new File(testDir, "configs");
+        if (proto.exists()) {
+            proto.delete();
+        }
+
+        final Locale arabic = new Locale.Builder().setLocale(new Locale("ar", "AE")).build();
+        final Locale urdu = new Locale.Builder().setLocale(new Locale("ur", "IN")).build();
+        final Locale urduExtension = new Locale.Builder().setLocale(new Locale("ur", "IN"))
+                .setExtension('u', "nu-latn").build();
+        Configuration write = new Configuration();
+        write.setLocales(new LocaleList(arabic, urdu, urduExtension));
+        writeToProto(proto, write);
+        assertTrue("Failed to write configs to proto.", proto.exists());
+
+        final Configuration read = new Configuration();
+        try {
+            readFromProto(proto, read);
+        } finally {
+            proto.delete();
+        }
+
+        assertEquals("Missing locales in proto file written to disk.",
+                read.getLocales().size(), write.getLocales().size());
+        assertTrue("Arabic locale not found in Configuration locale list.",
+                read.getLocales().indexOf(arabic) != -1);
+        assertTrue("Urdu locale not found in Configuration locale list.",
+                read.getLocales().indexOf(urdu) != -1);
+        assertTrue("Urdu locale with extensions not found in Configuration locale list.",
+                read.getLocales().indexOf(urduExtension) != -1);
+    }
+
+    private void writeToProto(File f, Configuration config) throws Exception {
+        final AtomicFile af = new AtomicFile(f);
+        FileOutputStream fos = af.startWrite();
+        try {
+            final ProtoOutputStream protoOut = new ProtoOutputStream(fos);
+            final long token = protoOut.start(IntervalStatsProto.CONFIGURATIONS);
+            config.writeToProto(protoOut, IntervalStatsProto.Configuration.CONFIG, false, false);
+            protoOut.end(token);
+            protoOut.flush();
+            af.finishWrite(fos);
+            fos = null;
+        } finally {
+            af.failWrite(fos);
+        }
+    }
+
+    private void readFromProto(File f, Configuration config) throws Exception {
+        final AtomicFile afRead = new AtomicFile(f);
+        try (FileInputStream in = afRead.openRead()) {
+            final ProtoInputStream protoIn = new ProtoInputStream(in);
+            if (protoIn.nextField(IntervalStatsProto.CONFIGURATIONS)) {
+                final long token = protoIn.start(IntervalStatsProto.CONFIGURATIONS);
+                if (protoIn.nextField(IntervalStatsProto.Configuration.CONFIG)) {
+                    config.readFromProto(protoIn, IntervalStatsProto.Configuration.CONFIG);
+                    protoIn.end(token);
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index b749e71..ae4edb9 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -17,25 +17,25 @@
 
 package android.os;
 
-import androidx.test.filters.MediumTest;
-
 import junit.framework.TestCase;
 
 public class ProcessTest extends TestCase {
 
-    @MediumTest
+    private static final int BAD_PID = 0;
+
     public void testProcessGetUidFromName() throws Exception {
         assertEquals(android.os.Process.SYSTEM_UID, Process.getUidForName("system"));
         assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth"));
         assertEquals(Process.FIRST_APPLICATION_UID, Process.getUidForName("u0_a0"));
         assertEquals(UserHandle.getUid(1, Process.SYSTEM_UID), Process.getUidForName("u1_system"));
-        assertEquals(UserHandle.getUid(2, Process.FIRST_ISOLATED_UID),
+        assertEquals(UserHandle.getUid(2, Process.FIRST_APP_ZYGOTE_ISOLATED_UID),
                 Process.getUidForName("u2_i0"));
+        assertEquals(UserHandle.getUid(2, Process.FIRST_ISOLATED_UID),
+                Process.getUidForName("u2_i9000"));
         assertEquals(UserHandle.getUid(3, Process.FIRST_APPLICATION_UID + 100),
                 Process.getUidForName("u3_a100"));
     }
 
-    @MediumTest
     public void testProcessGetUidFromNameFailure() throws Exception {
         // Failure cases
         assertEquals(-1, Process.getUidForName("u2a_foo"));
@@ -47,4 +47,29 @@
         assertEquals(-1, Process.getUidForName("u2jhsajhfkjhsafkhskafhkashfkjashfkjhaskjfdhakj3"));
     }
 
+    /**
+     * Tests getUidForPid() by ensuring that it returns the correct value when the process queried
+     * doesn't exist.
+     */
+    public void testGetUidForPidInvalidPid() {
+        assertEquals(-1, Process.getUidForPid(BAD_PID));
+    }
+
+    /**
+     * Tests getParentPid() by ensuring that it returns the correct value when the process queried
+     * doesn't exist.
+     */
+    public void testGetParentPidInvalidPid() {
+        assertEquals(-1, Process.getParentPid(BAD_PID));
+    }
+
+    /**
+     * Tests getThreadGroupLeader() by ensuring that it returns the correct value when the
+     * thread queried doesn't exist.
+     */
+    public void testGetThreadGroupLeaderInvalidTid() {
+        // This function takes a TID instead of a PID but BAD_PID should also be a bad TID.
+        assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
+    }
+
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
deleted file mode 100644
index eef780a..0000000
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ /dev/null
@@ -1,804 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import static com.google.android.collect.Sets.newHashSet;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.is;
-
-import static java.lang.reflect.Modifier.isFinal;
-import static java.lang.reflect.Modifier.isPublic;
-import static java.lang.reflect.Modifier.isStatic;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/** Tests that ensure appropriate settings are backed up. */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SettingsBackupTest {
-
-    /**
-     * see {@link com.google.android.systemui.power.EnhancedEstimatesGoogleImpl} for more details
-     */
-    public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
-            "hybrid_sysui_battery_warning_flags";
-
-    /**
-     * The following blacklists contain settings that should *not* be backed up and restored to
-     * another device.  As a general rule, anything that is not user configurable should be
-     * blacklisted (and conversely, things that *are* user configurable *should* be backed up)
-     */
-    private static final Set<String> BACKUP_BLACKLISTED_SYSTEM_SETTINGS =
-            newHashSet(
-                    Settings.System.ADVANCED_SETTINGS, // candidate for backup?
-                    Settings.System.ALARM_ALERT_CACHE, // internal cache
-                    Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
-                    Settings.System.EGG_MODE, // I am the lolrus
-                    Settings.System.END_BUTTON_BEHAVIOR, // bug?
-                    Settings.System
-                            .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, // candidate for backup?
-                    Settings.System.LOCKSCREEN_DISABLED, // ?
-                    Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
-                    Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
-                    Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
-                    Settings.System.POINTER_LOCATION, // backup candidate?
-                    Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
-                    Settings.System.RINGTONE_CACHE, // internal cache
-                    Settings.System.SCREEN_BRIGHTNESS, // removed in P
-                    Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
-                    Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
-                    Settings.System.SHOW_TOUCHES, // bug?
-                    Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
-                    Settings.System.SIP_ALWAYS, // value, not a setting
-                    Settings.System.SYSTEM_LOCALES, // bug?
-                    Settings.System.USER_ROTATION, // backup candidate?
-                    Settings.System.VIBRATE_IN_SILENT, // deprecated?
-                    Settings.System.VIBRATE_ON, // candidate for backup?
-                    Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
-                                                          // not change volume
-                    Settings.System.VOLUME_ALARM, // deprecated since API 2?
-                    Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
-                    Settings.System.VOLUME_MASTER, // candidate for backup?
-                    Settings.System.VOLUME_MUSIC, // deprecated since API 2?
-                    Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
-                    Settings.System.VOLUME_RING, // deprecated since API 2?
-                    Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
-                    Settings.System.VOLUME_VOICE, // deprecated since API 2?
-                    Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
-                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
-                    Settings.System.PEAK_REFRESH_RATE // depends on hardware capabilities
-                    );
-
-    private static final Set<String> BACKUP_BLACKLISTED_GLOBAL_SETTINGS =
-            newHashSet(
-                    Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
-                    Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
-                    Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
-                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
-                    Settings.Global.ADB_ENABLED,
-                    Settings.Global.ADD_USERS_WHEN_LOCKED,
-                    Settings.Global.AIRPLANE_MODE_ON,
-                    Settings.Global.AIRPLANE_MODE_RADIOS,
-                    Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
-                    Settings.Global.ALARM_MANAGER_CONSTANTS,
-                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
-                    Settings.Global.ALWAYS_FINISH_ACTIVITIES,
-                    Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
-                    Settings.Global.ANIMATOR_DURATION_SCALE,
-                    Settings.Global.ANOMALY_DETECTION_CONSTANTS,
-                    Settings.Global.ANOMALY_CONFIG,
-                    Settings.Global.ANOMALY_CONFIG_VERSION,
-                    Settings.Global.APN_DB_UPDATE_CONTENT_URL,
-                    Settings.Global.APN_DB_UPDATE_METADATA_URL,
-                    Settings.Global.APP_BINDING_CONSTANTS,
-                    Settings.Global.APP_IDLE_CONSTANTS,
-                    Settings.Global.APP_OPS_CONSTANTS,
-                    Settings.Global.APP_STANDBY_ENABLED,
-                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
-                    Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE,
-                    Settings.Global.ASSISTED_GPS_ENABLED,
-                    Settings.Global.AUDIO_SAFE_VOLUME_STATE,
-                    Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-                    Settings.Global.AUTOFILL_LOGGING_LEVEL,
-                    Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
-                    Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
-                    Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
-                    Settings.Global.AVERAGE_TIME_TO_DISCHARGE,
-                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
-                    Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME,
-                    Settings.Global.BROADCAST_BG_CONSTANTS,
-                    Settings.Global.BROADCAST_FG_CONSTANTS,
-                    Settings.Global.BROADCAST_OFFLOAD_CONSTANTS,
-                    Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
-                    Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
-                    Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS,
-                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
-                    Settings.Global.BATTERY_STATS_CONSTANTS,
-                    Settings.Global.BINDER_CALLS_STATS,
-                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
-                    Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
-                    Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
-                    Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS,
-                    Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS,
-                    Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
-                    Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
-                    Settings.Global.BLE_SCAN_BACKGROUND_MODE,
-                    Settings.Global.BLOCKED_SLICES,
-                    Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
-                    Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
-                    Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE,
-                    Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
-                    Settings.Global.BLUETOOTH_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX,
-                    Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
-                    Settings.Global.BLUETOOTH_DISABLED_PROFILES,
-                    Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST,
-                    Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
-                    Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
-                    Settings.Global.BOOT_COUNT,
-                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
-                    Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
-                    Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
-                    Settings.Global.CAPTIVE_PORTAL_MODE,
-                    Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
-                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS,
-                    Settings.Global.CAPTIVE_PORTAL_SERVER,
-                    Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
-                    Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
-                    Settings.Global.CAR_DOCK_SOUND,
-                    Settings.Global.CARRIER_APP_WHITELIST,
-                    Settings.Global.CARRIER_APP_NAMES,
-                    Settings.Global.CAR_UNDOCK_SOUND,
-                    Settings.Global.CDMA_CELL_BROADCAST_SMS,
-                    Settings.Global.CDMA_ROAMING_MODE,
-                    Settings.Global.CDMA_SUBSCRIPTION_MODE,
-                    Settings.Global.CELL_ON,
-                    Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
-                    Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
-                    Settings.Global.COMPATIBILITY_MODE,
-                    Settings.Global.CONNECTIVITY_CHANGE_DELAY,
-                    Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
-                    Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
-                    Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
-                    Settings.Global.CONVERSATION_ACTIONS_UPDATE_CONTENT_URL,
-                    Settings.Global.CONVERSATION_ACTIONS_UPDATE_METADATA_URL,
-                    Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
-                    Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
-                    Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
-                    Settings.Global.DATABASE_CREATION_BUILDID,
-                    Settings.Global.DATABASE_DOWNGRADE_REASON,
-                    Settings.Global.DATA_ROAMING,
-                    Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
-                    Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
-                    Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK,
-                    Settings.Global.DEBUG_APP,
-                    Settings.Global.DEBUG_VIEW_ATTRIBUTES,
-                    Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE,
-                    Settings.Global.DEFAULT_DNS_SERVER,
-                    Settings.Global.DEFAULT_INSTALL_LOCATION,
-                    Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
-                    Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
-                    Settings.Global.DESK_DOCK_SOUND,
-                    Settings.Global.DESK_UNDOCK_SOUND,
-                    Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
-                    Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
-                    Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
-                    Settings.Global.DEVELOPMENT_FORCE_RTL,
-                    Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
-                    Settings.Global.DEVICE_DEMO_MODE,
-                    Settings.Global.DEVICE_IDLE_CONSTANTS,
-                    Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
-                    Settings.Global.BATTERY_SAVER_CONSTANTS,
-                    Settings.Global.BATTERY_TIP_CONSTANTS,
-                    Settings.Global.DEFAULT_SM_DP_PLUS,
-                    Settings.Global.DEVICE_NAME,
-                    Settings.Global.DEVICE_POLICY_CONSTANTS,
-                    Settings.Global.DEVICE_PROVISIONED,
-                    Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
-                    Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
-                    Settings.Global.DISPLAY_PANEL_LPM,
-                    Settings.Global.DISPLAY_SCALING_FORCE,
-                    Settings.Global.DISPLAY_SIZE_FORCED,
-                    Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
-                    Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
-                    Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
-                    Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
-                    Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
-                    Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
-                    Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
-                    Settings.Global.DROPBOX_AGE_SECONDS,
-                    Settings.Global.DROPBOX_MAX_FILES,
-                    Settings.Global.DROPBOX_QUOTA_KB,
-                    Settings.Global.DROPBOX_QUOTA_PERCENT,
-                    Settings.Global.DROPBOX_RESERVE_PERCENT,
-                    Settings.Global.DROPBOX_TAG_PREFIX,
-                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
-                    Settings.Global.EMULATE_DISPLAY_CUTOUT,
-                    Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
-                    Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
-                    Settings.Global.ENABLE_CELLULAR_ON_BOOT,
-                    Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
-                    Settings.Global.ENABLE_DISKSTATS_LOGGING,
-                    Settings.Global.ENABLE_EPHEMERAL_FEATURE,
-                    Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
-                    Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
-                    Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
-                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
-                    Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                    Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
-                    Settings.Global.ERROR_LOGCAT_PREFIX,
-                    Settings.Global.EUICC_PROVISIONED,
-                    Settings.Global.EUICC_SUPPORTED_COUNTRIES,
-                    Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
-                    Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
-                    Settings.Global.FANCY_IME_ANIMATIONS,
-                    Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
-                    Settings.Global.FORCED_APP_STANDBY_ENABLED,
-                    Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
-                    Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED,
-                    Settings.Global.FSTRIM_MANDATORY_INTERVAL,
-                    Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
-                    Settings.Global.GLOBAL_HTTP_PROXY_HOST,
-                    Settings.Global.GLOBAL_HTTP_PROXY_PAC,
-                    Settings.Global.GLOBAL_HTTP_PROXY_PORT,
-                    Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
-                    Settings.Global.GNSS_SATELLITE_BLACKLIST,
-                    Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
-                    Settings.Global.HDMI_CEC_SWITCH_ENABLED,
-                    Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
-                    Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
-                    Settings.Global.HDMI_CONTROL_ENABLED,
-                    Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
-                    Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                    Settings.Global.HIDDEN_API_POLICY,
-                    Settings.Global.HIDE_ERROR_DIALOGS,
-                    Settings.Global.HTTP_PROXY,
-                    HYBRID_SYSUI_BATTERY_WARNING_FLAGS,
-                    Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
-                    Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
-                    Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
-                    Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
-                    Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
-                    Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                    Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
-                    Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS,
-                    Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
-                    Settings.Global.KERNEL_CPU_THREAD_READER,
-                    Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
-                    Settings.Global.LANG_ID_UPDATE_METADATA_URL,
-                    Settings.Global.LAST_ACTIVE_USER_ID,
-                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
-                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
-                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
-                    Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
-                    Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS,
-                    Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
-                    Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
-                    Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
-                    Settings.Global.LOCK_SOUND,
-                    Settings.Global.LOOPER_STATS,
-                    Settings.Global.LOW_BATTERY_SOUND,
-                    Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
-                    Settings.Global.LOW_POWER_MODE,
-                    Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
-                    Settings.Global.LOW_POWER_MODE_STICKY,
-                    Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
-                    Settings.Global.LTE_SERVICE_FORCED,
-                    Settings.Global.LID_BEHAVIOR,
-                    Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
-                    Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
-                    Settings.Global.MDC_INITIAL_MAX_RETRY,
-                    Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
-                    Settings.Global.MHL_POWER_CHARGE_ENABLED,
-                    Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
-                    Settings.Global.MOBILE_DATA, // Candidate for backup?
-                    Settings.Global.MOBILE_DATA_ALWAYS_ON,
-                    Settings.Global.MODE_RINGER,
-                    Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
-                    Settings.Global.MULTI_SIM_SMS_PROMPT,
-                    Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
-                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                    Settings.Global.MULTI_SIM_VOICE_PROMPT,
-                    Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
-                    Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
-                    Settings.Global.NETSTATS_DEV_DELETE_AGE,
-                    Settings.Global.NETSTATS_DEV_PERSIST_BYTES,
-                    Settings.Global.NETSTATS_DEV_ROTATE_AGE,
-                    Settings.Global.NETSTATS_ENABLED,
-                    Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES,
-                    Settings.Global.NETSTATS_POLL_INTERVAL,
-                    Settings.Global.NETSTATS_SAMPLE_ENABLED,
-                    Settings.Global.NETSTATS_AUGMENT_ENABLED,
-                    Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE,
-                    Settings.Global.NETSTATS_UID_BUCKET_DURATION,
-                    Settings.Global.NETSTATS_UID_DELETE_AGE,
-                    Settings.Global.NETSTATS_UID_PERSIST_BYTES,
-                    Settings.Global.NETSTATS_UID_ROTATE_AGE,
-                    Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION,
-                    Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
-                    Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
-                    Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
-                    Settings.Global.NETPOLICY_QUOTA_ENABLED,
-                    Settings.Global.NETPOLICY_QUOTA_UNLIMITED,
-                    Settings.Global.NETPOLICY_QUOTA_LIMITED,
-                    Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
-                    Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
-                    Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
-                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
-                    Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
-                    Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
-                    Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
-                    Settings.Global.NETWORK_PREFERENCE,
-                    Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
-                    Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
-                    Settings.Global.NETWORK_SCORER_APP,
-                    Settings.Global.NETWORK_SCORING_PROVISIONED,
-                    Settings.Global.NETWORK_SCORING_UI_ENABLED,
-                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
-                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
-                    Settings.Global.NETWORK_WATCHLIST_ENABLED,
-                    Settings.Global.NEW_CONTACT_AGGREGATOR,
-                    Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
-                    Settings.Global.NITZ_UPDATE_DIFF,
-                    Settings.Global.NITZ_UPDATE_SPACING,
-                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
-                    Settings.Global.NSD_ON,
-                    Settings.Global.NTP_SERVER,
-                    Settings.Global.NTP_TIMEOUT,
-                    Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
-                    Settings.Global.OVERLAY_DISPLAY_DEVICES,
-                    Settings.Global.PAC_CHANGE_DELAY,
-                    Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
-                    Settings.Global.PACKAGE_VERIFIER_ENABLE,
-                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
-                    Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
-                    Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
-                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT,
-                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
-                    Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
-                    Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
-                    Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS,
-                    Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
-                    Settings.Global.POLICY_CONTROL,
-                    Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE,
-                    Settings.Global.POWER_MANAGER_CONSTANTS,
-                    Settings.Global.PREFERRED_NETWORK_MODE,
-                    Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
-                    Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
-                    Settings.Global.RADIO_BLUETOOTH,
-                    Settings.Global.RADIO_CELL,
-                    Settings.Global.RADIO_NFC,
-                    Settings.Global.RADIO_WIFI,
-                    Settings.Global.RADIO_WIMAX,
-                    Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
-                    Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
-                    Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
-                    Settings.Global.SAFE_BOOT_DISALLOWED,
-                    Settings.Global.SELINUX_STATUS,
-                    Settings.Global.SELINUX_UPDATE_CONTENT_URL,
-                    Settings.Global.SELINUX_UPDATE_METADATA_URL,
-                    Settings.Global.SEND_ACTION_APP_ERROR,
-                    Settings.Global.SET_GLOBAL_HTTP_PROXY,
-                    Settings.Global.SET_INSTALL_LOCATION,
-                    Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL,
-                    Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST,
-                    Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
-                    Settings.Global.SETTINGS_USE_EXTERNAL_PROVIDER_API,
-                    Settings.Global.SETTINGS_USE_PSD_API,
-                    Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
-                    Settings.Global.SHOW_FIRST_CRASH_DIALOG,
-                    Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED,
-                    Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
-                    Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED,
-                    Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
-                    Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
-                    Settings.Global.SHOW_TEMPERATURE_WARNING,
-                    Settings.Global.SHOW_USB_TEMPERATURE_ALARM,
-                    Settings.Global.SIGNED_CONFIG_VERSION,
-                    Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
-                    Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
-                    Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
-                    Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
-                    Settings.Global.SMS_SHORT_CODE_CONFIRMATION,
-                    Settings.Global.SMS_SHORT_CODE_RULE,
-                    Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL,
-                    Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL,
-                    Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
-                    Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
-                    Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
-                    Settings.Global.STORAGE_BENCHMARK_INTERVAL,
-                    Settings.Global.STORAGE_SETTINGS_CLOBBER_THRESHOLD,
-                    Settings.Global.SYNC_MANAGER_CONSTANTS,
-                    Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
-                    Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
-                    Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
-                    Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
-                    Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
-                    Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
-                    Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
-                    Settings.Global.SYS_VDSO,
-                    Settings.Global.SYS_UIDCPUPOWER,
-                    Settings.Global.SYS_TRACED,
-                    Settings.Global.FPS_DEVISOR,
-                    Settings.Global.TCP_DEFAULT_INIT_RWND,
-                    Settings.Global.TETHER_DUN_APN,
-                    Settings.Global.TETHER_DUN_REQUIRED,
-                    Settings.Global.TETHER_OFFLOAD_DISABLED,
-                    Settings.Global.TETHER_SUPPORTED,
-                    Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER,
-                    Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
-                    Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS,
-                    Settings.Global.THEATER_MODE_ON,
-                    Settings.Global.TIME_ONLY_MODE_CONSTANTS,
-                    Settings.Global.TIME_REMAINING_ESTIMATE_MILLIS,
-                    Settings.Global.TIME_REMAINING_ESTIMATE_BASED_ON_USAGE,
-                    Settings.Global.TRANSITION_ANIMATION_SCALE,
-                    Settings.Global.TRUSTED_SOUND,
-                    Settings.Global.TZINFO_UPDATE_CONTENT_URL,
-                    Settings.Global.TZINFO_UPDATE_METADATA_URL,
-                    Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                    Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
-                    Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                    Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
-                    Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
-                    Settings.Global.UNGAZE_SLEEP_ENABLED,
-                    Settings.Global.UNLOCK_SOUND,
-                    Settings.Global.USE_GOOGLE_MAIL,
-                    Settings.Global.USER_ABSENT_RADIOS_OFF_FOR_SMALL_BATTERY_ENABLED,
-                    Settings.Global.USER_ABSENT_TOUCH_OFF_FOR_SMALL_BATTERY_ENABLED,
-                    Settings.Global.VT_IMS_ENABLED,
-                    Settings.Global.WAIT_FOR_DEBUGGER,
-                    Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
-                    Settings.Global.GPU_DEBUG_APP,
-                    Settings.Global.GPU_DEBUG_LAYERS,
-                    Settings.Global.GPU_DEBUG_LAYERS_GLES,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
-                    Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
-                    Settings.Global.GAME_DRIVER_ALL_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
-                    Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
-                    Settings.Global.GAME_DRIVER_BLACKLISTS,
-                    Settings.Global.GAME_DRIVER_BLACKLIST,
-                    Settings.Global.GAME_DRIVER_WHITELIST,
-                    Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
-                    Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
-                    Settings.Global.GPU_DEBUG_LAYER_APP,
-                    Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
-                    Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
-                    Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS,
-                    Settings.Global.USER_SWITCHER_ENABLED,
-                    Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
-                    Settings.Global.WARNING_TEMPERATURE,
-                    Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
-                    Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
-                    Settings.Global.WEBVIEW_MULTIPROCESS,
-                    Settings.Global.WEBVIEW_PROVIDER,
-                    Settings.Global.WFC_IMS_ENABLED,
-                    Settings.Global.WFC_IMS_MODE,
-                    Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                    Settings.Global.WFC_IMS_ROAMING_MODE,
-                    Settings.Global.WIFI_ALWAYS_REQUESTED,
-                    Settings.Global.WIFI_BADGING_THRESHOLDS,
-                    Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
-                    Settings.Global.WIFI_COUNTRY_CODE,
-                    Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
-                    Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
-                    Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
-                    Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
-                    Settings.Global.WIFI_DISPLAY_ON,
-                    Settings.Global.WIFI_DISPLAY_WPS_CONFIG,
-                    Settings.Global.WIFI_ENHANCED_AUTO_JOIN,
-                    Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
-                    Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
-                    Settings.Global.WIFI_FREQUENCY_BAND,
-                    Settings.Global.WIFI_IDLE_MS,
-                    Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
-                    Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
-                    Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
-                    Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
-                    Settings.Global.WIFI_LINK_PROBING_ENABLED,
-                    Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
-                    Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
-                    Settings.Global.WIFI_NETWORK_SHOW_RSSI,
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
-                    Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT,
-                    Settings.Global.WIFI_ON,
-                    Settings.Global.WIFI_P2P_DEVICE_NAME,
-                    Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
-                    Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
-                    Settings.Global.WIFI_SAVED_STATE,
-                    Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
-                    Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
-                    Settings.Global.WIFI_SCAN_THROTTLE_ENABLED,
-                    Settings.Global.WIFI_SCORE_PARAMS,
-                    Settings.Global.WIFI_SLEEP_POLICY,
-                    Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
-                    Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
-                    Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
-                    Settings.Global.WIFI_WATCHDOG_ON,
-                    Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                    Settings.Global.CHARGING_STARTED_SOUND,
-                    Settings.Global.WINDOW_ANIMATION_SCALE,
-                    Settings.Global.WTF_IS_FATAL,
-                    Settings.Global.ZEN_MODE,
-                    Settings.Global.ZEN_MODE_CONFIG_ETAG,
-                    Settings.Global.ZEN_MODE_RINGER_LEVEL,
-                    Settings.Global.ZRAM_ENABLED,
-                    Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION,
-                    Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
-                    Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
-                    Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
-                    Settings.Global.BACKUP_MULTI_USER_ENABLED,
-                    Settings.Global.ISOLATED_STORAGE_LOCAL,
-                    Settings.Global.ISOLATED_STORAGE_REMOTE,
-                    Settings.Global.APPOP_HISTORY_PARAMETERS,
-                    Settings.Global.APPOP_HISTORY_MODE,
-                    Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
-                    Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
-                    Settings.Global.ENABLE_RADIO_BUG_DETECTION,
-                    Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
-                    Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
-                    Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
-                    Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT,
-                    Settings.Global.POWER_BUTTON_LONG_PRESS,
-                    Settings.Global.POWER_BUTTON_VERY_LONG_PRESS);
-
-    private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
-             newHashSet(
-                 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
-                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, // Deprecated since O.
-                 Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
-                 Settings.Secure.ALWAYS_ON_VPN_APP,
-                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
-                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
-                 Settings.Secure.ANDROID_ID,
-                 Settings.Secure.ANR_SHOW_BACKGROUND,
-                 Settings.Secure.ASSISTANT,
-                 Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
-                 Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
-                 Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
-                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
-                 Settings.Secure.ASSIST_STRUCTURE_ENABLED,
-                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
-                 Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
-                 Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
-                 Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
-                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
-                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
-                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
-                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
-                 Settings.Secure.BACKUP_AUTO_RESTORE,
-                 Settings.Secure.BACKUP_ENABLED,
-                 Settings.Secure.BACKUP_PROVISIONED,
-                 Settings.Secure.BACKUP_TRANSPORT,
-                 Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
-                 Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
-                 Settings.Secure.CARRIER_APPS_HANDLED,
-                 Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
-                 Settings.Secure.COMPLETED_CATEGORY_PREFIX,
-                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
-                 Settings.Secure.CONTENT_CAPTURE_ENABLED,
-                 Settings.Secure.DEFAULT_INPUT_METHOD,
-                 Settings.Secure.DEVICE_PAIRED,
-                 Settings.Secure.DIALER_DEFAULT_APPLICATION,
-                 Settings.Secure.DISABLED_PRINT_SERVICES,
-                 Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
-                 Settings.Secure.DOCKED_CLOCK_FACE,
-                 Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
-                 Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
-                 Settings.Secure.ENABLED_INPUT_METHODS,  // Intentionally removed in P
-                 Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
-                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
-                 Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
-                 Settings.Secure.ENABLED_PRINT_SERVICES,
-                 Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE,
-                 Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED,
-                 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
-                 Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
-                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
-                 Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
-                 Settings.Secure.INSTALL_NON_MARKET_APPS,
-                 Settings.Secure.LAST_SETUP_SHOWN,
-                 Settings.Secure.LOCATION_CHANGER,
-                 Settings.Secure.LOCATION_MODE,
-                 Settings.Secure.LOCATION_PERMISSIONS_UPGRADE_TO_Q_MODE,
-                 Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
-                 Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
-                 Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
-                 Settings.Secure.MULTI_PRESS_TIMEOUT,
-                 Settings.Secure.NFC_PAYMENT_FOREGROUND,
-                 Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
-                 Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                 Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
-                 Settings.Secure.ODI_CAPTIONS_ENABLED,
-                 Settings.Secure.PACKAGE_VERIFIER_STATE,
-                 Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
-                 Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
-                 Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
-                 Settings.Secure.PRINT_SERVICE_SEARCH_URI,
-                 Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
-                 Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
-                 Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
-                 Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
-                 Settings.Secure.SEARCH_MAX_SHORTCUTS_RETURNED,
-                 Settings.Secure.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS,
-                 Settings.Secure.SEARCH_MAX_STAT_AGE_MILLIS,
-                 Settings.Secure.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING,
-                 Settings.Secure.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING,
-                 Settings.Secure.SEARCH_NUM_PROMOTED_SOURCES,
-                 Settings.Secure.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT,
-                 Settings.Secure.SEARCH_PREFILL_MILLIS,
-                 Settings.Secure.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS,
-                 Settings.Secure.SEARCH_QUERY_THREAD_CORE_POOL_SIZE,
-                 Settings.Secure.SEARCH_QUERY_THREAD_MAX_POOL_SIZE,
-                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE,
-                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE,
-                 Settings.Secure.SEARCH_SOURCE_TIMEOUT_MILLIS,
-                 Settings.Secure.SEARCH_THREAD_KEEPALIVE_SECONDS,
-                 Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT,
-                 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
-                 Settings.Secure.SELECTED_SPELL_CHECKER,  // Intentionally removed in Q
-                 Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,  // Intentionally removed in Q
-                 Settings.Secure.SETTINGS_CLASSNAME,
-                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
-                 Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
-                 Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
-                 Settings.Secure.SLEEP_TIMEOUT,
-                 Settings.Secure.SMS_DEFAULT_APPLICATION,
-                 Settings.Secure.SPELL_CHECKER_ENABLED,  // Intentionally removed in Q
-                 Settings.Secure.TRUST_AGENTS_INITIALIZED,
-                 Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
-                 Settings.Secure.TV_INPUT_CUSTOM_LABELS,
-                 Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
-                 Settings.Secure.TV_USER_SETUP_COMPLETE,
-                 Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED,
-                 Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS,
-                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
-                 Settings.Secure.USER_SETUP_COMPLETE,
-                 Settings.Secure.USER_SETUP_PERSONALIZATION_STATE,
-                 Settings.Secure.VOICE_INTERACTION_SERVICE,
-                 Settings.Secure.VOICE_RECOGNITION_SERVICE,
-                 Settings.Secure.INSTANT_APPS_ENABLED,
-                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
-                 Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS,
-                 Settings.Secure.KEYGUARD_SLICE_URI,
-                 Settings.Secure.PARENTAL_CONTROL_ENABLED,
-                 Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
-                 Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
-                 Settings.Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT,
-                 Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
-                 Settings.Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION,
-                 Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
-                 Settings.Secure.FLASHLIGHT_AVAILABLE,
-                 Settings.Secure.FLASHLIGHT_ENABLED,
-                 Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
-                 Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS,
-                 Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS,
-                 Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
-                 Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
-                 Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
-                 Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED);
-
-    @Test
-    public void systemSettingsBackedUpOrBlacklisted() {
-        checkSettingsBackedUpOrBlacklisted(
-                getCandidateSettings(Settings.System.class),
-                newHashSet(Settings.System.SETTINGS_TO_BACKUP),
-                BACKUP_BLACKLISTED_SYSTEM_SETTINGS);
-    }
-
-    @Test
-    public void globalSettingsBackedUpOrBlacklisted() {
-        checkSettingsBackedUpOrBlacklisted(
-            getCandidateSettings(Settings.Global.class),
-            newHashSet(Settings.Global.SETTINGS_TO_BACKUP),
-            BACKUP_BLACKLISTED_GLOBAL_SETTINGS);
-    }
-
-    @Test
-    public void secureSettingsBackedUpOrBlacklisted() {
-        HashSet<String> keys = new HashSet<String>();
-        Collections.addAll(keys, Settings.Secure.SETTINGS_TO_BACKUP);
-        Collections.addAll(keys, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
-        checkSettingsBackedUpOrBlacklisted(
-                getCandidateSettings(Settings.Secure.class),
-                keys,
-            BACKUP_BLACKLISTED_SECURE_SETTINGS);
-    }
-
-    private static void checkSettingsBackedUpOrBlacklisted(
-            Set<String> settings, Set<String> settingsToBackup, Set<String> blacklist) {
-        Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
-        Set<String> settingsNotBackedUpOrBlacklisted = difference(settingsNotBackedUp, blacklist);
-        assertThat(
-                "Settings not backed up or blacklisted",
-                settingsNotBackedUpOrBlacklisted,
-                is(empty()));
-
-        assertThat(
-                "blacklisted settings backed up",
-                intersect(settingsToBackup, blacklist),
-                is(empty()));
-    }
-
-    private static Set<String> getCandidateSettings(Class<? extends Settings.NameValueTable> clazz) {
-        HashSet<String> result = new HashSet<String>();
-        for (Field field : clazz.getDeclaredFields()) {
-            if (looksLikeValidSetting(field)) {
-                try {
-                    result.add((String) field.get(null));
-                } catch (IllegalAccessException e) {
-                    // Impossible for public fields
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-        return result;
-    }
-
-    private static boolean looksLikeValidSetting(Field field) {
-        int modifiers = field.getModifiers();
-        return isPublic(modifiers)
-                && isStatic(modifiers)
-                && isFinal(modifiers)
-                && field.getType() == String.class
-                && field.getAnnotation(Deprecated.class) == null;
-    }
-
-    private static <T> Set<T> difference(Set<T> s1, Set<T> s2) {
-        HashSet<T> result = new HashSet<T>(s1);
-        result.removeAll(s2);
-        return result;
-    }
-
-    private static <T> Set<T> intersect(Set<T> s1, Set<T> s2) {
-        HashSet<T> result = new HashSet<T>(s1);
-        result.retainAll(s2);
-        return result;
-    }
-
-}
diff --git a/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java
deleted file mode 100644
index 5f042d3..0000000
--- a/core/tests/coretests/src/android/provider/settings/validators/SettingsValidatorsTest.java
+++ /dev/null
@@ -1,340 +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 android.provider.settings.validators;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Map;
-
-/**
- * Tests that ensure all backed up settings have non-null validators. Also, common validators
- * are tested.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class SettingsValidatorsTest {
-
-    @Test
-    public void testNonNegativeIntegerValidator() {
-        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("1"));
-        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("0"));
-        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("-1"));
-        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("rectangle"));
-    }
-
-    @Test
-    public void testNonNegativeIntegerValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testAnyIntegerValidator() {
-        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("1"));
-        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("0"));
-        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("-1"));
-        assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("rectangle"));
-    }
-
-    @Test
-    public void testAnyIntegerValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testUriValidator_onNullValue_returnsTrue() {
-        assertTrue(SettingsValidators.URI_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testComponentNameValidator() {
-        assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(
-                "com.android.localtransport/.LocalTransport"));
-        assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle"));
-    }
-
-    @Test
-    public void testComponentNameValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testLenientIpAddressValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testNullableComponentNameValidator_onValidComponentName_returnsTrue() {
-        assertTrue(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(
-                "com.android.localtransport/.LocalTransport"));
-    }
-
-    @Test
-    public void testNullableComponentNameValidator_onInvalidComponentName_returnsFalse() {
-        assertFalse(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(
-                "rectangle"));
-    }
-
-    @Test
-    public void testNullableComponentNameValidator_onNullValue_returnsTrue() {
-        assertTrue(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testLocaleValidator() {
-        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("en_US"));
-        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("es"));
-        assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate("rectangle"));
-    }
-
-    @Test
-    public void testLocaleValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testPackageNameValidator() {
-        assertTrue(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(
-                "com.google.android"));
-        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate("com.google.@android"));
-        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.android"));
-        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.5android"));
-    }
-
-    @Test
-    public void testPackageNameValidator_onNullValue_returnsFalse() {
-        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testDiscreteValueValidator() {
-        String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
-        Validator v = new DiscreteValueValidator(beerTypes);
-        assertTrue(v.validate("Ale"));
-        assertTrue(v.validate("American IPA"));
-        assertTrue(v.validate("Stout"));
-        assertFalse(v.validate("Cider")); // just juice pretending to be beer
-    }
-
-    @Test
-    public void testDiscreteValueValidator_onNullValue_returnsFalse() {
-        String[] discreteTypes = new String[]{"Type1", "Type2"};
-        Validator v = new DiscreteValueValidator(discreteTypes);
-
-        assertFalse(v.validate(null));
-    }
-
-    @Test
-    public void testInclusiveIntegerRangeValidator() {
-        Validator v = new InclusiveIntegerRangeValidator(0, 5);
-        assertTrue(v.validate("0"));
-        assertTrue(v.validate("2"));
-        assertTrue(v.validate("5"));
-        assertFalse(v.validate("-1"));
-        assertFalse(v.validate("6"));
-    }
-
-    @Test
-    public void testInclusiveIntegerRangeValidator_onNullValue_returnsFalse() {
-        Validator v = new InclusiveIntegerRangeValidator(0, 5);
-
-        assertFalse(v.validate(null));
-    }
-
-    @Test
-    public void testInclusiveFloatRangeValidator() {
-        Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
-        assertTrue(v.validate("0.0"));
-        assertTrue(v.validate("2.0"));
-        assertTrue(v.validate("5.0"));
-        assertFalse(v.validate("-1.0"));
-        assertFalse(v.validate("6.0"));
-    }
-
-    @Test
-    public void testInclusiveFloatRangeValidator_onNullValue_returnsFalse() {
-        Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
-
-        assertFalse(v.validate(null));
-    }
-
-    @Test
-    public void testComponentNameListValidator() {
-        Validator v = new ComponentNameListValidator(",");
-        assertTrue(v.validate("com.android.localtransport/.LocalTransport,"
-                + "com.google.android.gms/.backup.migrate.service.D2dTransport"));
-        assertFalse(v.validate("com.google.5android,android"));
-    }
-
-    @Test
-    public void testComponentNameListValidator_onNullValue_returnsFalse() {
-        Validator v = new ComponentNameListValidator(",");
-
-        assertFalse(v.validate(null));
-    }
-
-    @Test
-    public void testPackageNameListValidator() {
-        Validator v = new PackageNameListValidator(",");
-        assertTrue(v.validate("com.android.localtransport.LocalTransport,com.google.android.gms"));
-        assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
-    }
-
-    @Test
-    public void testPackageNameListValidator_onNullValue_returnsFalse() {
-        Validator v = new PackageNameListValidator(",");
-
-        assertFalse(v.validate(null));
-    }
-
-    @Test
-    public void dateFormatValidator_onNullValue_returnsFalse() {
-        assertFalse(Settings.System.DATE_FORMAT_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testJSONObjectValidator() throws JSONException {
-        Validator v = SettingsValidators.JSON_OBJECT_VALIDATOR;
-
-        assertThat(v.validate(new JSONObject().toString())).isTrue();
-        assertThat(v.validate("{}")).isTrue();
-        assertThat(v.validate(new JSONObject().put("foo", "bar").toString()))
-                .isTrue();
-        assertThat(v.validate("{\"foo\": \"bar\"}")).isTrue();
-
-        assertThat(v.validate("random string")).isFalse();
-        assertThat(v.validate("random: string")).isFalse();
-        assertThat(v.validate("{random: }")).isFalse();
-    }
-
-    @Test
-    public void testJSONObjectValidator_onNullValue_returnsFalse() {
-        assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate(null)).isFalse();
-    }
-
-    @Test
-    public void testJSONObjectValidator_onEmptyString_returnsFalse() {
-        assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate("")).isFalse();
-    }
-
-    @Test
-    public void ensureAllBackedUpSystemSettingsHaveValidators() {
-        String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
-                Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
-
-        failIfOffendersPresent(offenders, "Settings.System");
-    }
-
-    @Test
-    public void testTTSListValidator_withValidInput_returnsTrue() {
-        assertTrue(
-                SettingsValidators.TTS_LIST_VALIDATOR.validate(
-                        "com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES"));
-    }
-
-    @Test
-    public void testTTSListValidator_withInvalidInput_returnsFalse() {
-        assertFalse(
-                SettingsValidators.TTS_LIST_VALIDATOR.validate(
-                        "com.foo.ttsengine:eng-USA,INVALID"));
-    }
-
-    @Test
-    public void testTTSListValidator_withEmptyInput_returnsFalse() {
-        assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(""));
-    }
-
-    @Test
-    public void testTTSListValidator_withNullInput_returnsFalse() {
-        assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void testTileListValidator_withValidInput_returnsTrue() {
-        assertTrue(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,2,3,4"));
-    }
-
-    @Test
-    public void testTileListValidator_withMissingValue_returnsFalse() {
-        assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,,3"));
-    }
-
-    @Test
-    public void testTileListValidator_withNullInput_returnsFalse() {
-        assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate(null));
-    }
-
-    @Test
-    public void ensureAllBackedUpGlobalSettingsHaveValidators() {
-        String offenders = getOffenders(concat(Settings.Global.SETTINGS_TO_BACKUP,
-                Settings.Global.LEGACY_RESTORE_SETTINGS), Settings.Global.VALIDATORS);
-
-        failIfOffendersPresent(offenders, "Settings.Global");
-    }
-
-    @Test
-    public void ensureAllBackedUpSecureSettingsHaveValidators() {
-        String offenders = getOffenders(concat(Settings.Secure.SETTINGS_TO_BACKUP,
-                Settings.Secure.LEGACY_RESTORE_SETTINGS), Settings.Secure.VALIDATORS);
-
-        failIfOffendersPresent(offenders, "Settings.Secure");
-    }
-
-    private void failIfOffendersPresent(String offenders, String settingsType) {
-        if (offenders.length() > 0) {
-            fail("All " + settingsType + " settings that are backed up have to have a non-null"
-                    + " validator, but those don't: " + offenders);
-        }
-    }
-
-    private String getOffenders(String[] settingsToBackup, Map<String, Validator> validators) {
-        StringBuilder offenders = new StringBuilder();
-        for (String setting : settingsToBackup) {
-            if (validators.get(setting) == null) {
-                offenders.append(setting).append(" ");
-            }
-        }
-        return offenders.toString();
-    }
-
-    private String[] concat(String[] first, String[] second) {
-        if (second == null || second.length == 0) {
-            return first;
-        }
-        final int firstLen = first.length;
-        final int secondLen = second.length;
-        String[] both = new String[firstLen + secondLen];
-        System.arraycopy(first, 0, both, 0, firstLen);
-        System.arraycopy(second, 0, both, firstLen, secondLen);
-        return both;
-    }
-}
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 6161108..a5a98a9 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -19,6 +19,8 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -181,6 +183,22 @@
                 logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE));
     }
 
+    @Test
+    public void testIsAppGroup() {
+        StatusBarNotification sbn = getNotification(PKG, GROUP_ID_1, CHANNEL_ID);
+        assertTrue(sbn.isAppGroup());
+
+        sbn = getNotification(PKG, null, CHANNEL_ID);
+        assertFalse(sbn.isAppGroup());
+
+        Notification.Builder nb = getNotificationBuilder(null, CHANNEL_ID)
+                .setSortKey("something");
+
+        sbn = getNotification(PKG, nb);
+        assertTrue(sbn.isAppGroup());
+
+    }
+
     private StatusBarNotification getNotification(String pkg, String group, String channelId) {
         return getNotification(pkg, getNotificationBuilder(group, channelId));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index bdd3038..be2c4e96 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -86,7 +86,7 @@
     }
 
     @AfterClass
-    public static void tearDownOnce() throws Exception {
+    public static void tearDownOnce() {
         sInsetsModeSession.close();
     }
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 1a22a70..6bce651 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.View;
 
 import androidx.test.filters.LargeTest;
@@ -51,12 +53,17 @@
 public class AccessibilityCacheTest {
     private static final int WINDOW_ID_1 = 0xBEEF;
     private static final int WINDOW_ID_2 = 0xFACE;
+    private static final int WINDOW_ID_3 = 0xABCD;
+    private static final int WINDOW_ID_4 = 0xDCBA;
     private static final int SINGLE_VIEW_ID = 0xCAFE;
     private static final int OTHER_VIEW_ID = 0xCAB2;
     private static final int PARENT_VIEW_ID = 0xFED4;
     private static final int CHILD_VIEW_ID = 0xFEED;
     private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
     private static final int MOCK_CONNECTION_ID = 1;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int DEFAULT_WINDOW_LAYER = 0;
+    private static final int SPECIFIC_WINDOW_LAYER = 5;
 
     AccessibilityCache mAccessibilityCache;
     AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
@@ -70,7 +77,7 @@
 
     @After
     public void tearDown() {
-        // Make sure we're recycling all of our window and node infos
+        // Make sure we're recycling all of our window and node infos.
         mAccessibilityCache.clear();
         AccessibilityInteractionClient.getInstance().clearCache();
     }
@@ -78,7 +85,7 @@
     @Test
     public void testEmptyCache_returnsNull() {
         assertNull(mAccessibilityCache.getNode(0, 0));
-        assertNull(mAccessibilityCache.getWindows());
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
         assertNull(mAccessibilityCache.getWindow(0));
     }
 
@@ -114,10 +121,11 @@
         try {
             windowInfo = AccessibilityWindowInfo.obtain();
             windowInfo.setId(WINDOW_ID_1);
+            windowInfo.setDisplayId(Display.DEFAULT_DISPLAY);
             mAccessibilityCache.addWindow(windowInfo);
             // Make a copy
             copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
-            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
+            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info.
             windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
             assertEquals(copyOfInfo, windowFromCache);
         } finally {
@@ -129,39 +137,40 @@
 
     @Test
     public void addWindowThenClear_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.clear();
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
     }
 
     @Test
     public void addWindowGetOtherId_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
     }
 
     @Test
     public void addWindowThenGetWindows_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
-        assertNull(mAccessibilityCache.getWindows());
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
     }
 
     @Test
     public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
-        AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
-        AccessibilityWindowInfo window1Out = null, window2Out = null;
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
         List<AccessibilityWindowInfo> windowsOut = null;
         try {
-            windowInfo1 = AccessibilityWindowInfo.obtain();
-            windowInfo1.setId(WINDOW_ID_1);
-            windowInfo1.setLayer(5);
-            windowInfo2 = AccessibilityWindowInfo.obtain();
-            windowInfo2.setId(WINDOW_ID_2);
-            windowInfo2.setLayer(windowInfo1.getLayer() + 1);
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
-            mAccessibilityCache.setWindows(windowsIn);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
 
-            windowsOut = mAccessibilityCache.getWindows();
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
 
@@ -182,8 +191,151 @@
     }
 
     @Test
+    public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo window3Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+
+            assertEquals(3, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(2));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            assertEquals(window3Out, windowsOut.get(1));
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            window3Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void
+            setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            // Sets windows to default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+            // Adds one window to second display.
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo windowInfo3 = null;
+        AccessibilityWindowInfo windowInfo4 = null;
+        AccessibilityWindowInfo window3Out = null;
+        AccessibilityWindowInfo window4Out = null;
+        List<AccessibilityWindowInfo> windowsOut1 = null;
+        List<AccessibilityWindowInfo> windowsOut2 = null;
+        try {
+            // Prepares all windows for default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
+            List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2);
+            // Prepares all windows for second display.
+            windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2);
+            windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3);
+            List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4);
+            // Sets all windows of all displays into A11y cache.
+            SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+            allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
+            allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
+            mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+            // Gets windows at default display.
+            windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut1.size());
+            assertEquals(windowInfo2, windowsOut1.get(0));
+            assertEquals(windowInfo1, windowsOut1.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            // Gets windows at seocnd display.
+            windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+            window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4);
+
+            assertEquals(2, windowsOut2.size());
+            assertEquals(windowInfo4, windowsOut2.get(0));
+            assertEquals(windowInfo3, windowsOut2.get(1));
+            assertEquals(windowInfo3, window3Out);
+            assertEquals(windowInfo4, window4Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            window3Out.recycle();
+            window4Out.recycle();
+            windowInfo3.recycle();
+            windowInfo4.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut1) {
+                windowInfo.recycle();
+            }
+            for (AccessibilityWindowInfo windowInfo : windowsOut2) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
     public void addWindowThenStateChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -191,7 +343,8 @@
 
     @Test
     public void addWindowThenWindowsChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -622,9 +775,16 @@
         }
     }
 
-    private void putWindowWithIdInCache(int id) {
+    private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
         AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
-        windowInfo.setId(id);
+        windowInfo.setId(windowId);
+        windowInfo.setLayer(layer);
+        return windowInfo;
+    }
+
+    private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) {
+        AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer);
+        windowInfo.setDisplayId(displayId);
         mAccessibilityCache.addWindow(windowInfo);
         windowInfo.recycle();
     }
@@ -713,4 +873,20 @@
             }
         }
     }
+
+    private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
+        SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+        allWindows.put(displayId, windows);
+        mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+    }
+
+    private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                mAccessibilityCache.getWindowsOnAllDisplays();
+
+        if (allWindows != null && allWindows.size() > 0) {
+            return allWindows.get(displayId);
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 4c59207..1bd52af 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 34;
+    private static final int NUM_MARSHALLED_PROPERTIES = 35;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 81ce15a..c5da549 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -162,6 +162,11 @@
         }
 
         @Override
+        public void internalNotifySessionLifecycle(boolean started) {
+            throw new UnsupportedOperationException("Should not have been called");
+        }
+
+        @Override
         public void updateContentCaptureContext(ContentCaptureContext context) {
             throw new UnsupportedOperationException("should not have been called");
         }
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
index e1ccd75..8891d3f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
@@ -65,8 +65,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
                 InstrumentationRegistry.getContext(),
@@ -101,8 +103,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
                 InstrumentationRegistry.getContext(),
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
index b9a1a8c..bcea5fe 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
@@ -83,9 +83,11 @@
                         null,
                         null,
                         null,
+                        null,
                         createRemoteActionTemplates(),
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents =
                 mTemplateClassificationIntentFactory.create(
@@ -124,9 +126,11 @@
                         null,
                         null,
                         null,
+                        null,
                         createRemoteActionTemplates(),
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents =
                 mTemplateClassificationIntentFactory.create(
@@ -162,8 +166,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         mTemplateClassificationIntentFactory.create(
                 InstrumentationRegistry.getContext(),
@@ -196,9 +202,11 @@
                         null,
                         null,
                         null,
+                        null,
                         new RemoteActionTemplate[0],
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         mTemplateClassificationIntentFactory.create(
                 InstrumentationRegistry.getContext(),
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 767ec0e..cc6eeed 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -24,12 +24,19 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.internal.app.ChooserActivity.CALLER_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserActivity.SHORTCUT_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_DEFAULT;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -45,6 +52,8 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager.ShareShortcutInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -472,6 +481,36 @@
     }
 
     @Test
+    public void copyTextToClipboardLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+            Mockito.anyBoolean(),
+            Mockito.anyBoolean(),
+            Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        MetricsLogger mockLogger = sOverrides.metricsLogger;
+        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.copy_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.copy_button)).perform(click());
+
+        verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
+        // First is  Activity shown, Second is "with preview"
+        assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
+                is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET));
+        assertThat(logMakerCaptor
+                        .getAllValues().get(2)
+                        .getSubtype(),
+                is(1));
+    }
+
+    @Test
     public void oneVisibleImagePreview() throws InterruptedException {
         Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
                 + com.android.frameworks.coretests.R.drawable.test320x240);
@@ -770,6 +809,139 @@
         onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
     }
 
+    @Test
+    public void testGetBaseScore() {
+        final float testBaseScore = 0.89f;
+
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getScore(Mockito.isA(
+                ResolverActivity.DisplayResolveInfo.class))).thenReturn(testBaseScore);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        final ResolverActivity.DisplayResolveInfo testDri =
+                activity.createTestDisplayResolveInfo(sendIntent,
+                ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent);
+        final ChooserActivity.ChooserListAdapter adapter = activity.getAdapter();
+
+        assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
+                is(SHORTCUT_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
+                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
+    }
+
+    /**
+     * The case when AppPrediction service is not defined in PackageManager is already covered
+     * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
+     * case when the prediction service is defined but the component is not available on the device.
+     */
+    @Test
+    public void testIsAppPredictionServiceAvailable() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
+            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
+        } else {
+            assertThat(activity.isAppPredictionServiceAvailable(), is(true));
+
+            sOverrides.resources = Mockito.spy(activity.getResources());
+            when(sOverrides.resources.getString(R.string.config_defaultAppPredictionService))
+                    .thenReturn("ComponentNameThatDoesNotExist");
+
+            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
+        }
+    }
+
+    @Test
+    public void testConvertToChooserTarget_predictionService() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
+
+        int[] expectedOrderAllShortcuts = {0, 1, 2, 3};
+        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.98f, 0.97f};
+
+        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
+                null, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderAllShortcuts, expectedScoreAllShortcuts);
+
+        List<ShareShortcutInfo> subset = new ArrayList<>();
+        subset.add(shortcuts.get(1));
+        subset.add(shortcuts.get(2));
+        subset.add(shortcuts.get(3));
+
+        int[] expectedOrderSubset = {1, 2, 3};
+        float[] expectedScoreSubset = {0.99f, 0.98f, 0.97f};
+
+        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
+                TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderSubset, expectedScoreSubset);
+    }
+
+    @Test
+    public void testConvertToChooserTarget_shortcutManager() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
+
+        int[] expectedOrderAllShortcuts = {2, 0, 3, 1};
+        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.99f, 0.98f};
+
+        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
+                null, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderAllShortcuts, expectedScoreAllShortcuts);
+
+        List<ShareShortcutInfo> subset = new ArrayList<>();
+        subset.add(shortcuts.get(1));
+        subset.add(shortcuts.get(2));
+        subset.add(shortcuts.get(3));
+
+        int[] expectedOrderSubset = {2, 3, 1};
+        float[] expectedScoreSubset = {1.0f, 0.99f, 0.98f};
+
+        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
+                TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderSubset, expectedScoreSubset);
+    }
+
     // This test is too long and too slow and should not be taken as an example for future tests.
     @Test
     public void testDirectTargetSelectionLogging() throws InterruptedException {
@@ -800,7 +972,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -866,7 +1038,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -927,7 +1099,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -1066,4 +1238,43 @@
 
         return bitmap;
     }
+
+    private List<ShareShortcutInfo> createShortcuts(Context context) {
+        Intent testIntent = new Intent("TestIntent");
+
+        List<ShareShortcutInfo> shortcuts = new ArrayList<>();
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut1")
+                        .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0  2
+                new ComponentName("package1", "class1")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut2")
+                        .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1  3
+                new ComponentName("package2", "class2")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut3")
+                        .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2  0
+                new ComponentName("package3", "class3")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut4")
+                        .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3  2
+                new ComponentName("package4", "class4")));
+
+        return shortcuts;
+    }
+
+    private void assertCorrectShortcutToChooserTargetConversion(List<ShareShortcutInfo> shortcuts,
+            List<ChooserTarget> chooserTargets, int[] expectedOrder, float[] expectedScores) {
+        assertEquals(expectedOrder.length, chooserTargets.size());
+        for (int i = 0; i < chooserTargets.size(); i++) {
+            ChooserTarget ct = chooserTargets.get(i);
+            ShortcutInfo si = shortcuts.get(expectedOrder[i]).getShortcutInfo();
+            ComponentName cn = shortcuts.get(expectedOrder[i]).getTargetComponent();
+
+            assertEquals(si.getId(), ct.getIntentExtras().getString(Intent.EXTRA_SHORTCUT_ID));
+            assertEquals(si.getShortLabel(), ct.getTitle());
+            assertThat(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001, is(true));
+            assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 44e56ea..1d567c7 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -85,6 +86,14 @@
     }
 
     @Override
+    public Resources getResources() {
+        if (sOverrides.resources != null) {
+            return sOverrides.resources;
+        }
+        return super.getResources();
+    }
+
+    @Override
     protected Bitmap loadThumbnail(Uri uri, Size size) {
         if (sOverrides.previewThumbnail != null) {
             return sOverrides.previewThumbnail;
@@ -145,6 +154,7 @@
         public Bitmap previewThumbnail;
         public MetricsLogger metricsLogger;
         public int alternateProfileSetting;
+        public Resources resources;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -157,6 +167,7 @@
             resolverListController = mock(ResolverListController.class);
             metricsLogger = mock(MetricsLogger.class);
             alternateProfileSetting = 0;
+            resources = null;
         }
     }
 }
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0a729a9..2141b81 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -194,6 +194,16 @@
         }
 
         @Override
+        public void addHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+        }
+
+        @Override
+        public void removeHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+        }
+
+        @Override
         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
         }
 
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
new file mode 100644
index 0000000..3d54b68
--- /dev/null
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -0,0 +1,28 @@
+android_test {
+    name: "ScreenshotHelperTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+    
+    static_libs: [
+        "frameworks-base-testutils",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    certificate: "platform",
+}
+
diff --git a/core/tests/screenshothelpertests/AndroidManifest.xml b/core/tests/screenshothelpertests/AndroidManifest.xml
new file mode 100644
index 0000000..2e12ef4
--- /dev/null
+++ b/core/tests/screenshothelpertests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="com.android.internal.util"
+          android:sharedUserId="android.uid.systemui" >
+
+    <application >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.internal.util"
+            android:label="Screenshot Helper Tests" />
+
+            <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
+
+</manifest>
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
new file mode 100644
index 0000000..8483645
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.util;
+
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotHelperTest {
+    private Context mContext;
+    private ScreenshotHelper mScreenshotHelper;
+    private Handler mHandler;
+
+
+    @Before
+    public void setUp() {
+        // `ScreenshotHelper.notifyScreenshotError()` calls `Context.sendBroadcastAsUser()` and
+        // `Context.bindServiceAsUser`.
+        //
+        // This raises a `SecurityException` if the device is locked. Calling either `Context`
+        // method results in a broadcast of `android.intent.action. USER_PRESENT`. Only the system
+        // process is allowed to broadcast that `Intent`.
+        mContext = Mockito.spy(Context.class);
+        Mockito.doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+        Mockito.doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+
+        mHandler = new Handler(Looper.getMainLooper());
+        mScreenshotHelper = new ScreenshotHelper(mContext);
+    }
+
+    @Test
+    public void testFullscreenScreenshot() {
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+    }
+
+    @Test
+    public void testSelectedRegionScreenshot() {
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
+                null);
+    }
+
+    @Test
+    public void testScreenshotTimesOut() {
+        long timeoutMs = 10;
+
+        CountDownLatch lock = new CountDownLatch(1);
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+                mHandler,
+                worked -> {
+                    assertFalse(worked);
+                    lock.countDown();
+                });
+
+        try {
+            // Add tolerance for delay to prevent flakes.
+            long awaitDurationMs = timeoutMs + 100;
+            if (!lock.await(awaitDurationMs, TimeUnit.MILLISECONDS)) {
+                fail("lock never freed");
+            }
+        } catch (InterruptedException e) {
+            fail("lock interrupted");
+        }
+    }
+}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 4493f3a..7e167c9 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -133,3 +133,8 @@
     sub_dir: "permissions",
     src: "com.android.timezone.updater.xml",
 }
+
+filegroup {
+    name: "services.core.protolog.json",
+    srcs: ["services.core.protolog.json"],
+}
diff --git a/data/etc/TEST_MAPPING b/data/etc/TEST_MAPPING
new file mode 100644
index 0000000..1a5db2f
--- /dev/null
+++ b/data/etc/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+    "presubmit": [
+        {
+            "file_patterns": ["(/|^)platform.xml"],
+            "name": "CtsPermission2TestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission2.cts.RuntimePermissionProperties"
+                }
+            ]
+        }
+    ]
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 9272ea5..c445651 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -128,3 +128,10 @@
     filename_from_src: true,
     product_specific: true,
 }
+
+prebuilt_etc {
+    name: "privapp_whitelist_com.android.car.floatingcardslauncher",
+    sub_dir: "permissions",
+    src: "com.android.car.floatingcardslauncher.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.floatingcardslauncher.xml b/data/etc/car/com.android.car.floatingcardslauncher.xml
new file mode 100644
index 0000000..2755fee
--- /dev/null
+++ b/data/etc/car/com.android.car.floatingcardslauncher.xml
@@ -0,0 +1,25 @@
+<?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
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.floatingcardslauncher">
+        <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.settings.xml b/data/etc/car/com.android.car.settings.xml
index 5f7e1c1..291ce80 100644
--- a/data/etc/car/com.android.car.settings.xml
+++ b/data/etc/car/com.android.car.settings.xml
@@ -50,7 +50,6 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.USER_ACTIVITY"/>
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
-        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
     </privapp-permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 233f826..2ab7845a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -166,6 +166,7 @@
     <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="cameraserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
     <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.ACCESS_SURFACE_FLINGER" uid="graphics" />
 
@@ -205,6 +206,10 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="29">
+        <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
+    </split-permission>
 
     <!-- This is a list of all the libraries available for application
          code to link against. -->
@@ -224,6 +229,8 @@
     <library name="android.hidl.manager-V1.0-java"
             file="/system/framework/android.hidl.manager-V1.0-java.jar"
             dependency="android.hidl.base-V1.0-java" />
+    <library name="telephony-common"
+        file="/system/framework/telephony-common.jar" />
 
     <!-- These are the standard packages that are white-listed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
new file mode 100644
index 0000000..261891f
--- /dev/null
+++ b/data/etc/services.core.protolog.json
@@ -0,0 +1,15 @@
+{
+  "version": "1.0.0",
+  "messages": {
+    "485522692": {
+      "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+      "level": "ERROR",
+      "group": "TEST_GROUP"
+    }
+  },
+  "groups": {
+    "TEST_GROUP": {
+      "tag": "WindowManagetProtoLogTest"
+    }
+  }
+}
diff --git a/data/keyboards/Vendor_045e_Product_02dd.kl b/data/keyboards/Vendor_045e_Product_02dd.kl
new file mode 100644
index 0000000..3975cec
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02dd.kl
@@ -0,0 +1,57 @@
+# 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.
+
+#
+# XBox One Controller - Model 1697 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 314   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315   BUTTON_START
+
+# Xbox key
+key 316   BUTTON_MODE
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index e6eaa696..96ac0f9 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -207,7 +207,7 @@
     }
 
     public GradientDrawable() {
-        this(new GradientState(Orientation.LEFT_RIGHT, null), null);
+        this(new GradientState(Orientation.TOP_BOTTOM, null), null);
     }
 
     /**
@@ -637,7 +637,7 @@
      * @see #setOrientation(Orientation)
      */
     public Orientation getOrientation() {
-        return mGradientState.getOrientation();
+        return mGradientState.mOrientation;
     }
 
     /**
@@ -653,7 +653,7 @@
      * @see #getOrientation()
      */
     public void setOrientation(Orientation orientation) {
-        mGradientState.setOrientation(orientation);
+        mGradientState.mOrientation = orientation;
         mGradientIsDirty = true;
         invalidateSelf();
     }
@@ -1270,7 +1270,7 @@
 
                 if (st.mGradient == LINEAR_GRADIENT) {
                     final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
-                    switch (st.getOrientation()) {
+                    switch (st.mOrientation) {
                     case TOP_BOTTOM:
                         x0 = r.left;            y0 = r.top;
                         x1 = x0;                y1 = level * r.bottom;
@@ -1759,6 +1759,33 @@
         int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
         st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
 
+        switch (st.mAngle) {
+            case 0:
+                st.mOrientation = Orientation.LEFT_RIGHT;
+                break;
+            case 45:
+                st.mOrientation = Orientation.BL_TR;
+                break;
+            case 90:
+                st.mOrientation = Orientation.BOTTOM_TOP;
+                break;
+            case 135:
+                st.mOrientation = Orientation.BR_TL;
+                break;
+            case 180:
+                st.mOrientation = Orientation.RIGHT_LEFT;
+                break;
+            case 225:
+                st.mOrientation = Orientation.TR_BL;
+                break;
+            case 270:
+                st.mOrientation = Orientation.TOP_BOTTOM;
+                break;
+            case 315:
+                st.mOrientation = Orientation.TL_BR;
+                break;
+        }
+
         final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
         if (tv != null) {
             final float radius;
@@ -1981,7 +2008,7 @@
         int[] mAttrPadding;
 
         public GradientState(Orientation orientation, int[] gradientColors) {
-            setOrientation(orientation);
+            mOrientation = orientation;
             setGradientColors(gradientColors);
         }
 
@@ -2184,93 +2211,11 @@
             mCenterY = y;
         }
 
-        public void setOrientation(Orientation orientation) {
-            // Update the angle here so that subsequent attempts to obtain the orientation
-            // from the angle overwrite previously configured values during inflation
-            mAngle = getAngleFromOrientation(orientation);
-            mOrientation = orientation;
-        }
-
         @NonNull
         public Orientation getOrientation() {
-            updateGradientStateOrientation();
             return mOrientation;
         }
 
-        /**
-         * Update the orientation of the gradient based on the given angle only if the type is
-         * {@link #LINEAR_GRADIENT}
-         */
-        private void updateGradientStateOrientation() {
-            if (mGradient == LINEAR_GRADIENT) {
-                int angle = mAngle;
-                if (angle % 45 != 0) {
-                    throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
-                            + "to be a multiple of 45");
-                }
-
-                Orientation orientation;
-                switch (angle) {
-                    case 0:
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                    case 45:
-                        orientation = Orientation.BL_TR;
-                        break;
-                    case 90:
-                        orientation = Orientation.BOTTOM_TOP;
-                        break;
-                    case 135:
-                        orientation = Orientation.BR_TL;
-                        break;
-                    case 180:
-                        orientation = Orientation.RIGHT_LEFT;
-                        break;
-                    case 225:
-                        orientation = Orientation.TR_BL;
-                        break;
-                    case 270:
-                        orientation = Orientation.TOP_BOTTOM;
-                        break;
-                    case 315:
-                        orientation = Orientation.TL_BR;
-                        break;
-                    default:
-                        // Should not get here as exception is thrown above if angle is not multiple
-                        // of 45 degrees
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                }
-                mOrientation = orientation;
-            }
-        }
-
-        private int getAngleFromOrientation(@Nullable Orientation orientation) {
-            if (orientation != null) {
-                switch (orientation) {
-                    default:
-                    case LEFT_RIGHT:
-                        return 0;
-                    case BL_TR:
-                        return 45;
-                    case BOTTOM_TOP:
-                        return 90;
-                    case BR_TL:
-                        return 135;
-                    case RIGHT_LEFT:
-                        return 180;
-                    case TR_BL:
-                        return 225;
-                    case TOP_BOTTOM:
-                        return 270;
-                    case TL_BR:
-                        return 315;
-                }
-            } else {
-                return 0;
-            }
-        }
-
         public void setGradientColors(@Nullable int[] colors) {
             mGradientColors = colors;
             mSolidColors = null;
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 08f4176..54995ac 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -16,11 +16,12 @@
 
 package android.security;
 
+import android.annotation.UnsupportedAppUsage;
+
 import com.android.org.bouncycastle.util.io.pem.PemObject;
 import com.android.org.bouncycastle.util.io.pem.PemReader;
 import com.android.org.bouncycastle.util.io.pem.PemWriter;
 
-import android.annotation.UnsupportedAppUsage;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -90,9 +91,9 @@
     public static final String EXTRA_INSTALL_AS_UID = "install_as_uid";
 
     /**
-     * Intent extra: name for the user's private key.
+     * Intent extra: name for the user's key pair.
      */
-    public static final String EXTRA_USER_PRIVATE_KEY_NAME = "user_private_key_name";
+    public static final String EXTRA_USER_KEY_ALIAS = "user_key_pair_name";
 
     /**
      * Intent extra: data for the user's private key in PEM-encoded PKCS#8.
@@ -100,21 +101,11 @@
     public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
 
     /**
-     * Intent extra: name for the user's certificate.
-     */
-    public static final String EXTRA_USER_CERTIFICATE_NAME = "user_certificate_name";
-
-    /**
      * Intent extra: data for the user's certificate in PEM-encoded X.509.
      */
     public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
 
     /**
-     * Intent extra: name for CA certificate chain
-     */
-    public static final String EXTRA_CA_CERTIFICATES_NAME = "ca_certificates_name";
-
-    /**
      * Intent extra: data for CA certificate chain in PEM-encoded X.509.
      */
     public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b3cdff7..97da3cc 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -43,7 +43,8 @@
     String installCaCertificate(in byte[] caCertificate);
 
     // APIs used by DevicePolicyManager
-    boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
+    boolean installKeyPair(
+        in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid);
     boolean removeKeyPair(String alias);
 
     // APIs used by Settings
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 9f4a619..cf2ef30 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -116,6 +116,7 @@
 
   if (result != 0) {
     LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+    ::CloseArchive(unmanaged_handle);
     return {};
   }
 
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 8a035db..5353869 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -7402,12 +7402,12 @@
         print_complex(value.data, true);
         printf("\n");
     } else if (value.dataType >= Res_value::TYPE_FIRST_COLOR_INT
-            || value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
+            && value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
         printf("(color) #%08x\n", value.data);
     } else if (value.dataType == Res_value::TYPE_INT_BOOLEAN) {
         printf("(boolean) %s\n", value.data ? "true" : "false");
     } else if (value.dataType >= Res_value::TYPE_FIRST_INT
-            || value.dataType <= Res_value::TYPE_LAST_INT) {
+            && value.dataType <= Res_value::TYPE_LAST_INT) {
         printf("(int) 0x%08x or %d\n", value.data, value.data);
     } else {
         printf("(unknown type) t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)\n",
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 2d2df52..b39f4f2 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -130,7 +130,7 @@
         // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
         temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
     }
-    out->join(RECT_ARGS(temp));
+    out->join({RECT_ARGS(temp)});
 }
 
 void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
@@ -145,7 +145,7 @@
             // Don't attempt to calculate damage for a perspective transform
             // as the numbers this works with can break the perspective
             // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
-            rect->set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+            rect->setLTRB(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
         }
     }
 }
@@ -209,7 +209,7 @@
 
     // Perform clipping
     if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
-        if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
+        if (!frame->pendingDirty.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
             frame->pendingDirty.setEmpty();
         }
     }
@@ -233,7 +233,7 @@
 }
 
 void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
-    mHead->pendingDirty.join(left, top, right, bottom);
+    mHead->pendingDirty.join({left, top, right, bottom});
 }
 
 void DamageAccumulator::peekAtDirty(SkRect* dest) const {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 54ac0bd..2314072 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -163,7 +163,7 @@
     SkCanvas::SaveLayerFlags layerFlags = 0;
 
     if (!(flags & SaveFlags::ClipToLayer)) {
-        layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
+        layerFlags |= SkCanvasPriv::kDontClipToLayer_SaveLayerFlag;
     }
 
     return layerFlags;
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index a1be5b7..2ba6fbe 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -361,7 +361,7 @@
 
 void Bitmap::getBounds(SkRect* bounds) const {
     SkASSERT(bounds);
-    bounds->set(0, 0, SkIntToScalar(width()), SkIntToScalar(height()));
+    bounds->setIWH(width(), height());
 }
 
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 530926b..3010206 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -511,13 +511,13 @@
 }
 
 void SkiaPipeline::dumpResourceCacheUsage() const {
-    int resources, maxResources;
-    size_t bytes, maxBytes;
+    int resources;
+    size_t bytes;
     mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
-    mRenderThread.getGrContext()->getResourceCacheLimits(&maxResources, &maxBytes);
+    size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
 
     SkString log("Resource Cache Usage:\n");
-    log.appendf("%8d items out of %d maximum items\n", resources, maxResources);
+    log.appendf("%8d items\n", resources);
     log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
                 bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
 
@@ -569,6 +569,7 @@
     // Set up the overdraw canvas.
     SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height());
     sk_sp<SkSurface> offscreen = surface->makeSurface(offscreenInfo);
+    LOG_ALWAYS_FATAL_IF(!offscreen, "Failed to create offscreen SkSurface for overdraw viz.");
     SkOverdrawCanvas overdrawCanvas(offscreen->getCanvas());
 
     // Fake a redraw to replay the draw commands.  This will increment the alpha channel
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 5469a68..fc26813 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -69,8 +69,7 @@
 
     if (context) {
         mGrContext = std::move(context);
-        mGrContext->getResourceCacheLimits(&mMaxResources, nullptr);
-        mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes);
+        mGrContext->setResourceCacheLimit(mMaxResourceBytes);
     }
 }
 
@@ -119,8 +118,8 @@
             // limits between the background and max amounts. This causes the unlocked resources
             // that have persistent data to be purged in LRU order.
             mGrContext->purgeUnlockedResources(true);
-            mGrContext->setResourceCacheLimits(mMaxResources, mBackgroundResourceBytes);
-            mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes);
+            mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
             break;
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index ad251f2..d7977cc 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -71,7 +71,6 @@
     sk_sp<GrContext> mGrContext;
 #endif
 
-    int mMaxResources = 0;
     const size_t mMaxResourceBytes;
     const size_t mBackgroundResourceBytes;
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 88a0c6e..684dc22 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -15,7 +15,17 @@
  */
 
 #include "CanvasContext.h"
+
 #include <GpuMemoryTracker.h>
+#include <apex/window.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
 
 #include "../Properties.h"
 #include "AnimationContext.h"
@@ -32,16 +42,6 @@
 #include "utils/TimeUtils.h"
 #include "utils/TraceUtils.h"
 
-#include <strings.h>
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <algorithm>
-
-#include <cstdint>
-#include <cstdlib>
-#include <functional>
-
 #define TRIM_MEMORY_COMPLETE 80
 #define TRIM_MEMORY_UI_HIDDEN 20
 
@@ -146,7 +146,7 @@
     if (surface) {
         mNativeSurface = new ReliableSurface{std::move(surface)};
         // TODO: Fix error handling & re-shorten timeout
-        mNativeSurface->setDequeueTimeout(4000_ms);
+        ANativeWindow_setDequeueTimeout(mNativeSurface.get(), 4000_ms);
         mNativeSurface->enableFrameTimestamps(true);
     } else {
         mNativeSurface = nullptr;
@@ -484,18 +484,16 @@
         swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC);
         swap.vsyncTime = mRenderThread.timeLord().latestVsync();
         if (didDraw) {
-            int durationUs;
-            nsecs_t dequeueStart = mNativeSurface->getLastDequeueStartTime();
+            nsecs_t dequeueStart = ANativeWindow_getLastDequeueStartTime(mNativeSurface.get());
             if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
                 // Ignoring dequeue duration as it happened prior to frame render start
                 // and thus is not part of the frame.
                 swap.dequeueDuration = 0;
             } else {
-                mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs);
-                swap.dequeueDuration = us2ns(durationUs);
+                swap.dequeueDuration =
+                        us2ns(ANativeWindow_getLastDequeueDuration(mNativeSurface.get()));
             }
-            mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs);
-            swap.queueDuration = us2ns(durationUs);
+            swap.queueDuration = us2ns(ANativeWindow_getLastQueueDuration(mNativeSurface.get()));
         } else {
             swap.dequeueDuration = 0;
             swap.queueDuration = 0;
@@ -726,7 +724,7 @@
         // New surface needs a full draw
         dirty->setEmpty();
     } else {
-        if (!dirty->isEmpty() && !dirty->intersect(0, 0, frame.width(), frame.height())) {
+        if (!dirty->isEmpty() && !dirty->intersect(SkRect::MakeIWH(frame.width(), frame.height()))) {
             ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?", SK_RECT_ARGS(*dirty),
                   frame.width(), frame.height());
             dirty->setEmpty();
@@ -735,7 +733,7 @@
     }
 
     if (dirty->isEmpty()) {
-        dirty->set(0, 0, frame.width(), frame.height());
+        dirty->setIWH(frame.width(), frame.height());
     }
 
     // At this point dirty is the area of the window to update. However,
@@ -751,7 +749,7 @@
         if (frame.bufferAge() > (int)mSwapHistory.size()) {
             // We don't have enough history to handle this old of a buffer
             // Just do a full-draw
-            dirty->set(0, 0, frame.width(), frame.height());
+            dirty->setIWH(frame.width(), frame.height());
         } else {
             // At this point we haven't yet added the latest frame
             // to the damage history (happens below)
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index a44b804..864780f 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -308,4 +308,4 @@
     return result;
 }
 
-};  // namespace android::uirenderer::renderthread
\ No newline at end of file
+};  // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index 7f1a078..f768df3 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -31,16 +31,12 @@
     ReliableSurface(sp<Surface>&& surface);
     ~ReliableSurface();
 
-    void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); }
-
     int reserveNext();
 
     void allocateBuffers() { mSurface->allocateBuffers(); }
 
     int query(int what, int* value) const { return mSurface->query(what, value); }
 
-    nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); }
-
     uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); }
 
     int getAndClearError() {
@@ -105,4 +101,4 @@
     static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer);
 };
 
-};  // namespace android::uirenderer::renderthread
\ No newline at end of file
+};  // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 8b5912b..12c5b83 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -282,9 +282,9 @@
         return;
     }
     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
-    dprintf(fd, "\nVersion: %lld", proto->version_code());
-    dprintf(fd, "\nStats since: %lldns", proto->stats_start());
-    dprintf(fd, "\nStats end: %lldns", proto->stats_end());
+    dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
+    dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
+    dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
     auto summary = proto->summary();
     dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
     dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
index d5ecfaf..80b5cc1 100644
--- a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp
@@ -37,7 +37,7 @@
             SkRegion region;
             for (int xOffset = 0; xOffset < 200; xOffset += 2) {
                 for (int yOffset = 0; yOffset < 200; yOffset += 2) {
-                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+                    region.op({xOffset, yOffset, xOffset + 1, yOffset + 1}, SkRegion::kUnion_Op);
                 }
             }
 
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index a6af475..42bf03e 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -98,6 +98,7 @@
     bool write(uint64_t fieldId, double val);
     bool write(uint64_t fieldId, float val);
     bool write(uint64_t fieldId, int val);
+    bool write(uint64_t fieldId, long val);
     bool write(uint64_t fieldId, long long val);
     bool write(uint64_t fieldId, bool val);
     bool write(uint64_t fieldId, std::string val);
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 6cfa357..ea9b79a 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -116,6 +116,34 @@
 }
 
 bool
+ProtoOutputStream::write(uint64_t fieldId, long val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        default:
+            ALOGW("Field type %d is not supported when writing long val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
 ProtoOutputStream::write(uint64_t fieldId, long long val)
 {
     return internalWrite(fieldId, val, "long long");
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 36fe8da..2e2f984 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 /**
@@ -164,6 +165,7 @@
         return hasCapability(MEASUREMENT_CORRECTIONS_REFLECTING_PLANE);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("GnssCapabilities: ( ");
diff --git a/location/java/android/location/GnssMeasurementCorrections.java b/location/java/android/location/GnssMeasurementCorrections.java
index 3e32c21..a23213f 100644
--- a/location/java/android/location/GnssMeasurementCorrections.java
+++ b/location/java/android/location/GnssMeasurementCorrections.java
@@ -176,6 +176,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-29s = %s\n";
diff --git a/location/java/android/location/GnssReflectingPlane.java b/location/java/android/location/GnssReflectingPlane.java
index 9d05287..1acdd1e 100644
--- a/location/java/android/location/GnssReflectingPlane.java
+++ b/location/java/android/location/GnssReflectingPlane.java
@@ -107,6 +107,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-29s = %s\n";
diff --git a/location/java/android/location/GnssSingleSatCorrection.java b/location/java/android/location/GnssSingleSatCorrection.java
index e901909..aeca562 100644
--- a/location/java/android/location/GnssSingleSatCorrection.java
+++ b/location/java/android/location/GnssSingleSatCorrection.java
@@ -268,6 +268,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-29s = %s\n";
diff --git a/location/java/android/location/GpsClock.java b/location/java/android/location/GpsClock.java
index 52ba60e..f123766 100644
--- a/location/java/android/location/GpsClock.java
+++ b/location/java/android/location/GpsClock.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -437,6 +438,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-15s = %s\n";
diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java
index 51718b8..27a8189 100644
--- a/location/java/android/location/GpsMeasurement.java
+++ b/location/java/android/location/GpsMeasurement.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1244,6 +1245,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-29s = %s\n";
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index 1cd1fb4..d69158d 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -140,6 +140,7 @@
         parcel.writeTypedArray(measurementsArray, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n");
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index 77f0113..6eeea26 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -290,6 +290,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         final String format = "   %-15s = %s\n";
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index 2aa685c..f60e5c7 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -109,6 +109,7 @@
         parcel.writeParcelable(mNavigationMessage, flags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("[ GpsNavigationMessageEvent:\n\n");
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 97bc404..d06ba12 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -107,10 +107,6 @@
     List<LocationRequest> getTestProviderCurrentRequests(String provider, String opPackageName);
     LocationTime getGnssTimeMillis();
 
-    // --- deprecated ---
-    void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime,
-            String opPackageName);
-
     boolean sendExtraCommand(String provider, String command, inout Bundle extras);
 
     // --- internal ---
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1cc246b..5be4770 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1543,44 +1543,19 @@
     /**
      * This method has no effect as provider status has been deprecated and is no longer supported.
      *
-     * @param provider the provider name
-     * @param status the mock status
-     * @param extras a Bundle containing mock extras
-     * @param updateTime the mock update time
-     *
-     * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
-     * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
-     * allowed} for your app.
-     * @throws IllegalArgumentException if no provider with the given name exists
-     *
      * @deprecated This method has no effect.
      */
     @Deprecated
     public void setTestProviderStatus(
-            @NonNull String provider, int status, @Nullable Bundle extras, long updateTime) {
-        try {
-            mService.setTestProviderStatus(provider, status, extras, updateTime,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
+            @NonNull String provider, int status, @Nullable Bundle extras, long updateTime) {}
 
     /**
      * This method has no effect as provider status has been deprecated and is no longer supported.
      *
-     * @param provider the provider name
-     * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
-     * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
-     * allowed} for your app.
-     * @throws IllegalArgumentException if no provider with the given name exists
-     *
      * @deprecated This method has no effect.
      */
     @Deprecated
-    public void clearTestProviderStatus(@NonNull String provider) {
-        setTestProviderStatus(provider, LocationProvider.AVAILABLE, null, 0L);
-    }
+    public void clearTestProviderStatus(@NonNull String provider) {}
 
     /**
      * Get the last list of {@link LocationRequest}s sent to the provider.
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index a05d850..0902acf 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -734,6 +734,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
diff --git a/location/java/com/android/internal/location/ILocationProvider.aidl b/location/java/com/android/internal/location/ILocationProvider.aidl
index 8ae972b..4246c6c 100644
--- a/location/java/com/android/internal/location/ILocationProvider.aidl
+++ b/location/java/com/android/internal/location/ILocationProvider.aidl
@@ -37,10 +37,4 @@
 
     @UnsupportedAppUsage
     oneway void sendExtraCommand(String command, in Bundle extras);
-
-    // --- deprecated and will be removed the future ---
-    @UnsupportedAppUsage
-    int getStatus(out Bundle extras);
-    @UnsupportedAppUsage
-    long getStatusUpdateTime();
 }
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 274e0e4..07bfe02 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -31,7 +31,9 @@
 import com.android.internal.location.nano.GnssLogsProto.GnssLog;
 import com.android.internal.location.nano.GnssLogsProto.PowerMetrics;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 
 /**
  * GnssMetrics: Is used for logging GNSS metrics
@@ -63,9 +65,16 @@
     /* The time since boot when logging started */
     private String mLogStartInElapsedRealTime;
 
+    /** The number of hertz in one MHz */
+    private static final double HZ_PER_MHZ = 1e6;
+
     /* GNSS power metrics */
     private GnssPowerMetrics mGnssPowerMetrics;
 
+    /** Frequency range of GPS L5, Galileo E5a, QZSS J5 frequency band */
+    private static final double L5_CARRIER_FREQ_RANGE_LOW_HZ = 1164 * HZ_PER_MHZ;
+    private static final double L5_CARRIER_FREQ_RANGE_HIGH_HZ = 1189 * HZ_PER_MHZ;
+
     /* A boolean array indicating whether the constellation types have been used in fix. */
     private boolean[] mConstellationTypes;
     /** Location failure statistics */
@@ -76,6 +85,16 @@
     private Statistics mPositionAccuracyMeterStatistics;
     /** Top 4 average CN0 statistics */
     private Statistics mTopFourAverageCn0Statistics;
+    /** Top 4 average CN0 statistics L5 */
+    private Statistics mTopFourAverageCn0StatisticsL5;
+    /** Total number of sv status messages processed */
+    private int mNumSvStatus;
+    /** Total number of L5 sv status messages processed */
+    private int mNumL5SvStatus;
+    /** Total number of sv status messages processed, where sv is used in fix */
+    private int mNumSvStatusUsedInFix;
+    /** Total number of L5 sv status messages processed, where sv is used in fix */
+    private int mNumL5SvStatusUsedInFix;
 
     public GnssMetrics(IBatteryStats stats) {
         mGnssPowerMetrics = new GnssPowerMetrics(stats);
@@ -83,6 +102,11 @@
         mTimeToFirstFixSecStatistics = new Statistics();
         mPositionAccuracyMeterStatistics = new Statistics();
         mTopFourAverageCn0Statistics = new Statistics();
+        mTopFourAverageCn0StatisticsL5 = new Statistics();
+        mNumSvStatus = 0;
+        mNumL5SvStatus = 0;
+        mNumSvStatusUsedInFix = 0;
+        mNumL5SvStatusUsedInFix = 0;
         reset();
     }
 
@@ -127,8 +151,14 @@
 
     /**
      * Logs CN0 when at least 4 SVs are available
+     *
+     * @param cn0s
+     * @param numSv
+     * @param svCarrierFreqs
      */
-    public void logCn0(float[] cn0s, int numSv) {
+    public void logCn0(float[] cn0s, int numSv, float[] svCarrierFreqs) {
+        // Calculate L5 Cn0
+        logCn0L5(numSv, cn0s, svCarrierFreqs);
         if (numSv == 0 || cn0s == null || cn0s.length == 0 || cn0s.length < numSv) {
             if (numSv == 0) {
                 mGnssPowerMetrics.reportSignalQuality(null, 0);
@@ -150,6 +180,75 @@
             mTopFourAverageCn0Statistics.addItem(top4AvgCn0);
         }
     }
+    /* Helper function to check if a SV is L5 */
+    private static boolean isL5Sv(float carrierFreq) {
+        return (carrierFreq >= L5_CARRIER_FREQ_RANGE_LOW_HZ
+                && carrierFreq <= L5_CARRIER_FREQ_RANGE_HIGH_HZ);
+    }
+
+    /**
+    * Logs sv status data
+    *
+    * @param svCount
+    * @param svidWithFlags
+    * @param svCarrierFreqs
+    */
+    public void logSvStatus(int svCount, int[] svidWithFlags, float[] svCarrierFreqs) {
+        boolean isL5 = false;
+        // Calculate SvStatus Information
+        for (int i = 0; i < svCount; i++) {
+            if ((svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) != 0) {
+                mNumSvStatus++;
+                isL5 = isL5Sv(svCarrierFreqs[i]);
+                if (isL5) {
+                    mNumL5SvStatus++;
+                }
+                if ((svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
+                    mNumSvStatusUsedInFix++;
+                    if (isL5) {
+                        mNumL5SvStatusUsedInFix++;
+                    }
+                }
+            }
+        }
+        return;
+    }
+
+    /**
+    * Logs CN0 when at least 4 SVs are available L5 Only
+    *
+    * @param svCount
+    * @param cn0s
+    * @param svCarrierFreqs
+    */
+    private void logCn0L5(int svCount, float[] cn0s, float[] svCarrierFreqs) {
+        if (svCount == 0 || cn0s == null || cn0s.length == 0 || cn0s.length < svCount
+                || svCarrierFreqs == null || svCarrierFreqs.length == 0
+                || svCarrierFreqs.length < svCount) {
+            return;
+        }
+        // Create array list of all L5 satellites in report.
+        ArrayList<Float> CnoL5Array = new ArrayList();
+        for (int i = 0; i < svCount; i++) {
+            if (isL5Sv(svCarrierFreqs[i])) {
+                CnoL5Array.add(cn0s[i]);
+            }
+        }
+        if (CnoL5Array.size() == 0 || CnoL5Array.size() < 4) {
+            return;
+        }
+        int numSvL5 = CnoL5Array.size();
+        Collections.sort(CnoL5Array);
+        if (CnoL5Array.get(numSvL5 - 4) > 0.0) {
+            double top4AvgCn0 = 0.0;
+            for (int i = numSvL5 - 4; i < numSvL5; i++) {
+                top4AvgCn0 += (double) CnoL5Array.get(i);
+            }
+            top4AvgCn0 /= 4;
+            mTopFourAverageCn0StatisticsL5.addItem(top4AvgCn0);
+        }
+        return;
+    }
 
     /**
      * Logs that a constellation type has been observed.
@@ -189,6 +288,24 @@
             msg.standardDeviationTopFourAverageCn0DbHz =
                     mTopFourAverageCn0Statistics.getStandardDeviation();
         }
+        if (mNumSvStatus > 0) {
+            msg.numSvStatusProcessed = mNumSvStatus;
+        }
+        if (mNumL5SvStatus > 0) {
+            msg.numL5SvStatusProcessed = mNumL5SvStatus;
+        }
+        if (mNumSvStatusUsedInFix > 0) {
+            msg.numSvStatusUsedInFix = mNumSvStatusUsedInFix;
+        }
+        if (mNumL5SvStatusUsedInFix > 0) {
+            msg.numL5SvStatusUsedInFix = mNumL5SvStatusUsedInFix;
+        }
+        if (mTopFourAverageCn0StatisticsL5.getCount() > 0) {
+            msg.numL5TopFourAverageCn0Processed = mTopFourAverageCn0StatisticsL5.getCount();
+            msg.meanL5TopFourAverageCn0DbHz = mTopFourAverageCn0StatisticsL5.getMean();
+            msg.standardDeviationL5TopFourAverageCn0DbHz =
+                    mTopFourAverageCn0StatisticsL5.getStandardDeviation();
+        }
         msg.powerMetrics = mGnssPowerMetrics.buildProto();
         msg.hardwareRevision = SystemProperties.get("ro.boot.revision", "");
         String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT);
@@ -238,6 +355,24 @@
             s.append("  Top 4 Avg CN0 standard deviation (dB-Hz): ").append(
                     mTopFourAverageCn0Statistics.getStandardDeviation()).append("\n");
         }
+        s.append("  Total number of sv status messages processed: ").append(
+                mNumSvStatus).append("\n");
+        s.append("  Total number of L5 sv status messages processed: ").append(
+                mNumL5SvStatus).append("\n");
+        s.append("  Total number of sv status messages processed, "
+                + "where sv is used in fix: ").append(
+                mNumSvStatusUsedInFix).append("\n");
+        s.append("  Total number of L5 sv status messages processed, "
+                + "where sv is used in fix: ").append(
+                mNumL5SvStatusUsedInFix).append("\n");
+        s.append("  Number of L5 CN0 reports: ").append(
+                mTopFourAverageCn0StatisticsL5.getCount()).append("\n");
+        if (mTopFourAverageCn0StatisticsL5.getCount() > 0) {
+            s.append("  L5 Top 4 Avg CN0 mean (dB-Hz): ").append(
+                    mTopFourAverageCn0StatisticsL5.getMean()).append("\n");
+            s.append("  L5 Top 4 Avg CN0 standard deviation (dB-Hz): ").append(
+                    mTopFourAverageCn0StatisticsL5.getStandardDeviation()).append("\n");
+        }
         s.append("  Used-in-fix constellation types: ");
         for (int i = 0; i < mConstellationTypes.length; i++) {
             if (mConstellationTypes[i]) {
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b15cc5c..fe0f669 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -17,12 +17,10 @@
 java_sdk_library {
     name: "com.android.location.provider",
     srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
     libs: [
         "androidx.annotation_annotation",
+        "framework-all",
     ],
     api_packages: ["com.android.location.provider"],
-    srcs_lib: "framework-minus-apex",
-    // TODO(b/70046217): remove core/java and android below. It was added to provide definitions for
-    // types like android.os.Bundle
-    srcs_lib_whitelist_pkgs: ["android", "com.android.internal.location"],
 }
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 6bde3a8..fc7bff3 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -256,17 +256,6 @@
     /**
      * This method will no longer be invoked.
      *
-     * Returns a information on the status of this provider.
-     * <p>{@link android.location.LocationProvider#OUT_OF_SERVICE} is returned if the provider is
-     * out of service, and this is not expected to change in the near
-     * future; {@link android.location.LocationProvider#TEMPORARILY_UNAVAILABLE} is returned if
-     * the provider is temporarily unavailable but is expected to be
-     * available shortly; and {@link android.location.LocationProvider#AVAILABLE} is returned
-     * if the provider is currently available.
-     *
-     * <p>If extras is non-null, additional status information may be
-     * added to it in the form of provider-specific key/value pairs.
-     *
      * @deprecated This callback will be never be invoked on Android Q and above. This method should
      * only be implemented in location providers that need to support SDKs below Android Q. This
      * method may be removed in the future.
@@ -279,15 +268,6 @@
     /**
      * This method will no longer be invoked.
      *
-     * Returns the time at which the status was last updated. It is the
-     * responsibility of the provider to appropriately set this value using
-     * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
-     * there is a status update that it wishes to broadcast to all its
-     * listeners. The provider should be careful not to broadcast
-     * the same status again.
-     *
-     * @return time of last status update in millis since last reboot
-     *
      * @deprecated This callback will be never be invoked on Android Q and above. This method should
      * only be implemented in location providers that need to support SDKs below Android Q. This
      * method may be removed in the future.
@@ -332,16 +312,6 @@
         }
 
         @Override
-        public int getStatus(Bundle extras) {
-            return onGetStatus(extras);
-        }
-
-        @Override
-        public long getStatusUpdateTime() {
-            return onGetStatusUpdateTime();
-        }
-
-        @Override
         public void sendExtraCommand(String command, Bundle extras) {
             onSendExtraCommand(command, extras);
         }
diff --git a/media/Android.bp b/media/Android.bp
index ef32239..a59b3e7 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -45,13 +45,6 @@
 }
 
 filegroup {
-    name: "updatable-media-srcs-without-aidls",
-    srcs : [
-        ":mediasession2-srcs-without-aidls",
-    ],
-}
-
-filegroup {
     name: "mediasession2-srcs",
     srcs: [
         "apex/java/android/media/Controller2Link.java",
@@ -71,19 +64,6 @@
 }
 
 filegroup {
-    name: "mediasession2-srcs-without-aidls",
-    srcs: [
-        ":mediasession2-srcs",
-    ],
-    exclude_srcs: [
-        "apex/java/android/media/IMediaController2.aidl",
-        "apex/java/android/media/IMediaSession2.aidl",
-        "apex/java/android/media/IMediaSession2Service.aidl",
-    ],
-    path: "apex/java",
-}
-
-filegroup {
     name: "mediaplayer2-srcs",
     srcs: [
         "apex/java/android/media/CloseGuard.java",
@@ -113,21 +93,21 @@
 droidstubs {
     name: "updatable-media-stubs",
     srcs: [
-        ":updatable-media-srcs-without-aidls",
+        ":updatable-media-srcs",
         ":framework-media-annotation-srcs",
     ],
     args: metalava_updatable_media_args,
-    // Ideally, sdk_version here should be "current_system", but "current - 1" is used
-    // to avoid dependency cycle with framework.
-    sdk_version: "28",
+    aidl: {
+        // TODO(b/135922046) remove this
+        include_dirs: ["frameworks/base/core/java"],
+    },
+    sdk_version: "system_current",
 }
 
 java_library {
     name: "updatable_media_stubs",
     srcs: [":updatable-media-stubs"],
-    // Ideally, sdk_version here should be "current_system", but "current - 1" is used
-    // to avoid dependency cycle with framework.
-    sdk_version: "28",
+    sdk_version: "system_current",
 }
 
 java_library {
diff --git a/media/java/android/media/AudioFocusInfo.java b/media/java/android/media/AudioFocusInfo.java
index ee89509..675cf73 100644
--- a/media/java/android/media/AudioFocusInfo.java
+++ b/media/java/android/media/AudioFocusInfo.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -155,7 +156,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj)
             return true;
         if (obj == null)
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e29e569..7cd09de 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1184,8 +1184,15 @@
             int bufferSizeInBytes, int mode) {
         // If no attributes, OK
         // otherwise check attributes for USAGE_MEDIA and CONTENT_UNKNOWN, MUSIC, or MOVIE.
+        // Only consider flags that are not compatible with FLAG_DEEP_BUFFER. We include
+        // FLAG_DEEP_BUFFER because if set the request is explicit and
+        // shouldEnablePowerSaving() should return false.
+        final int flags = attributes.getAllFlags()
+                & (AudioAttributes.FLAG_DEEP_BUFFER | AudioAttributes.FLAG_LOW_LATENCY
+                    | AudioAttributes.FLAG_HW_AV_SYNC | AudioAttributes.FLAG_BEACON);
+
         if (attributes != null &&
-                (attributes.getAllFlags() != 0  // cannot have any special flags
+                (flags != 0  // cannot have any special flags
                 || attributes.getUsage() != AudioAttributes.USAGE_MEDIA
                 || (attributes.getContentType() != AudioAttributes.CONTENT_TYPE_UNKNOWN
                     && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MUSIC
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 55583d5..5b53565 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -62,6 +62,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
@@ -1451,6 +1452,37 @@
     }
 
     /**
+     * Returns whether ExifInterface currently supports parsing data from the specified mime type
+     * or not.
+     *
+     * @param mimeType the string value of mime type
+     */
+    public static boolean isSupportedMimeType(@NonNull String mimeType) {
+        if (mimeType == null) {
+            throw new NullPointerException("mimeType shouldn't be null");
+        }
+
+        switch (mimeType.toLowerCase(Locale.ROOT)) {
+            case "image/jpeg":
+            case "image/x-adobe-dng":
+            case "image/x-canon-cr2":
+            case "image/x-nikon-nef":
+            case "image/x-nikon-nrw":
+            case "image/x-sony-arw":
+            case "image/x-panasonic-rw2":
+            case "image/x-olympus-orf":
+            case "image/x-pentax-pef":
+            case "image/x-samsung-srw":
+            case "image/x-fuji-raf":
+            case "image/heic":
+            case "image/heif":
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
      * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in
      * the image file.
      *
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e456dad..2799d46 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -299,6 +299,7 @@
                 }
             };
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 6fd3342..92fb31b 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -1006,7 +1006,7 @@
          * @return {@code true} if equals, {@code false} otherwise
          */
         @Override
-        public boolean equals(Object obj) {
+        public boolean equals(@Nullable Object obj) {
             if (!(obj instanceof RemoteUserInfo)) {
                 return false;
             }
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ada77c5..dc400ad 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -383,4 +383,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Get the hardware sound trigger module properties currently loaded.
+     *
+     * @return The properties currently loaded. Returns null if no supported hardware loaded.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @Nullable
+    public SoundTrigger.ModuleProperties getModuleProperties() {
+
+        try {
+            return mSoundTriggerService.getModuleProperties();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
index 5b316be..b12f7c5 100644
--- a/media/java/android/media/tv/TvInputHardwareInfo.java
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -19,12 +19,14 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.tv.input.V1_0.Constants;
 import android.media.AudioManager;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
+
 import java.lang.annotation.Retention;
 
 /**
@@ -141,6 +143,7 @@
         return mCableConnectionStatus;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java
index f012b46..7ea93b4 100644
--- a/media/java/android/media/tv/TvStreamConfig.java
+++ b/media/java/android/media/tv/TvStreamConfig.java
@@ -16,6 +16,8 @@
 
 package android.media.tv;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -87,6 +89,7 @@
         return mGeneration;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TvStreamConfig {mStreamId=" + mStreamId + ";" + "mType=" + mType + ";mGeneration="
@@ -163,7 +166,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null) return false;
         if (!(obj instanceof TvStreamConfig)) return false;
 
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index e09eeb8..ea00d6e 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -394,6 +394,7 @@
          *
          * @param encrypted The encryption status of the track.
          */
+        @NonNull
         public Builder setEncrypted(boolean encrypted) {
             mEncrypted = encrypted;
             return this;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c49b1e4..84fe27d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -52,7 +52,6 @@
         "libandroidfw",
         "libhidlallocatorutils",
         "libhidlbase",
-        "libhidltransport",
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hidl.memory@1.0",
@@ -147,7 +146,6 @@
         "android.hidl.memory@1.0",
         "libhidlbase",
         "libhidlmemory",
-        "libhidltransport",
         "libbinderthreadstate",
 
         // MediaPlayer2 implementation
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8d420e2..05aaa82 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -743,8 +743,10 @@
 }
 
 status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
-
-    status_t status = mCodec->getMetrics(reply);
+    mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+    status_t status = mCodec->getMetrics(reply2);
+    // getMetrics() updates reply2, pass the converted update along to our caller.
+    reply = MediaAnalyticsItem::convert(reply2);
     return status;
 }
 
@@ -1848,7 +1850,7 @@
     }
 
     // get what we have for the metrics from the codec
-    MediaAnalyticsItem *item = NULL;
+    MediaAnalyticsItem *item = 0;
 
     status_t err = codec->getMetrics(env, item);
     if (err != OK) {
@@ -1860,7 +1862,7 @@
 
     // housekeeping
     delete item;
-    item = NULL;
+    item = 0;
 
     return mybundle;
 }
diff --git a/media/jni/android_media_MediaDataSource.cpp b/media/jni/android_media_MediaDataSource.cpp
index 8c38d88..9705b91 100644
--- a/media/jni/android_media_MediaDataSource.cpp
+++ b/media/jni/android_media_MediaDataSource.cpp
@@ -106,7 +106,8 @@
     }
 
     ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread);
-    env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)mMemory->pointer());
+    env->GetByteArrayRegion(mByteArrayObj, 0, numread,
+        (jbyte*)mMemory->unsecurePointer());
     return numread;
 }
 
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index aa79ce0..c61365a 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -220,7 +220,7 @@
         return NO_MEMORY;
     }
 
-    memcpy(mMem->pointer(),
+    memcpy(mMem->unsecurePointer(),
             (const void*)((const uint8_t*)srcPtr + srcOffset), totalLength);
 
     DestinationBuffer dstBuffer;
@@ -248,7 +248,8 @@
 
     if (*status == Status::OK) {
         if (*bytesWritten > 0 && (ssize_t) *bytesWritten <= totalLength) {
-            memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *bytesWritten);
+            memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->unsecurePointer(),
+                *bytesWritten);
         } else {
             // status seems OK but bytesWritten is invalid, we really
             // have no idea what is wrong.
diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp
index 365e045..53adff3 100644
--- a/media/jni/android_media_MediaHTTPConnection.cpp
+++ b/media/jni/android_media_MediaHTTPConnection.cpp
@@ -148,7 +148,7 @@
                 byteArrayObj,
                 0,
                 n,
-                (jbyte *)conn->getIMemory()->pointer());
+                (jbyte *)conn->getIMemory()->unsecurePointer());
     }
 
     return n;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 3809bc4..18fd1a0 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -396,8 +396,12 @@
     // Call native method to retrieve a video frame
     VideoFrame *videoFrame = NULL;
     sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
+    // TODO: Using unsecurePointer() has some associated security pitfalls
+    //       (see declaration for details).
+    //       Either document why it is safe in this case or address the
+    //       issue (e.g. by copying).
     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
-        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+        videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
     }
     if (videoFrame == NULL) {
         ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
@@ -423,7 +427,11 @@
     VideoFrame *videoFrame = NULL;
     sp<IMemory> frameMemory = retriever->getImageAtIndex(index, colorFormat);
     if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
-        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+        // TODO: Using unsecurePointer() has some associated security pitfalls
+        //       (see declaration for details).
+        //       Either document why it is safe in this case or address the
+        //       issue (e.g. by copying).
+        videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
     }
     if (videoFrame == NULL) {
         ALOGE("getImageAtIndex: videoFrame is a NULL pointer");
@@ -454,7 +462,11 @@
     sp<IMemory> frameMemory = retriever->getImageAtIndex(
             index, colorFormat, true /*metaOnly*/, true /*thumbnail*/);
     if (frameMemory != 0) {
-        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+        // TODO: Using unsecurePointer() has some associated security pitfalls
+        //       (see declaration for details).
+        //       Either document why it is safe in this case or address the
+        //       issue (e.g. by copying).
+        videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
         int32_t thumbWidth = videoFrame->mWidth;
         int32_t thumbHeight = videoFrame->mHeight;
         videoFrame = NULL;
@@ -467,7 +479,11 @@
                 || thumbPixels * 6 >= maxPixels) {
             frameMemory = retriever->getImageAtIndex(
                     index, colorFormat, false /*metaOnly*/, true /*thumbnail*/);
-            videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+            // TODO: Using unsecurePointer() has some associated security pitfalls
+            //       (see declaration for details).
+            //       Either document why it is safe in this case or address the
+            //       issue (e.g. by copying).
+            videoFrame = static_cast<VideoFrame *>(frameMemory->unsecurePointer());
 
             if (thumbPixels > maxPixels) {
                 int downscale = ceil(sqrt(thumbPixels / (float)maxPixels));
@@ -514,11 +530,15 @@
     size_t i = 0;
     for (; i < numFrames; i++) {
         sp<IMemory> frame = retriever->getFrameAtIndex(frameIndex + i, colorFormat);
-        if (frame == NULL || frame->pointer() == NULL) {
+        if (frame == NULL || frame->unsecurePointer() == NULL) {
             ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
             break;
         }
-        VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->pointer());
+        // TODO: Using unsecurePointer() has some associated security pitfalls
+        //       (see declaration for details).
+        //       Either document why it is safe in this case or address the
+        //       issue (e.g. by copying).
+        VideoFrame *videoFrame = static_cast<VideoFrame *>(frame->unsecurePointer());
         jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1, outColorType);
         env->CallBooleanMethod(arrayList, fields.arrayListAdd, bitmapObj);
         env->DeleteLocalRef(bitmapObj);
@@ -551,7 +571,11 @@
     // the method name to getEmbeddedPicture().
     sp<IMemory> albumArtMemory = retriever->extractAlbumArt();
     if (albumArtMemory != 0) {  // cast the shared structure to a MediaAlbumArt object
-        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->pointer());
+        // TODO: Using unsecurePointer() has some associated security pitfalls
+        //       (see declaration for details).
+        //       Either document why it is safe in this case or address the
+        //       issue (e.g. by copying).
+        mediaAlbumArt = static_cast<MediaAlbumArt *>(albumArtMemory->unsecurePointer());
     }
     if (mediaAlbumArt == NULL) {
         ALOGE("getEmbeddedPicture: Call to getEmbeddedPicture failed.");
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 4906695..102bbf0 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -951,6 +951,8 @@
         ALOGV("process %p channel %d event %s",
               this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
                       "BUFFER_END");
+        // Only BUFFER_END should happen as we use static tracks.
+        setVolume_l(0.f, 0.f);  // set volume to 0 to indicate no need to ramp volume down.
         mSoundPool->addToStopList(this);
     } else if (event == AudioTrack::EVENT_LOOP_END) {
         ALOGV("End loop %p channel %d", this, mChannelID);
@@ -966,14 +968,18 @@
 bool SoundChannel::doStop_l()
 {
     if (mState != IDLE) {
-        setVolume_l(0, 0);
         ALOGV("stop");
-        // Since we're forcibly halting the previously playing content,
-        // we sleep here to ensure the volume is ramped down before we stop the track.
-        // Ideally the sleep time is the mixer period, or an approximation thereof
-        // (Fast vs Normal tracks are different).
-        // TODO: consider pausing instead of stop here.
-        std::this_thread::sleep_for(std::chrono::milliseconds(20));
+        if (mLeftVolume != 0.f || mRightVolume != 0.f) {
+            setVolume_l(0.f, 0.f);
+            if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
+                // Since we're forcibly halting the previously playing content,
+                // we sleep here to ensure the volume is ramped down before we stop the track.
+                // Ideally the sleep time is the mixer period, or an approximation thereof
+                // (Fast vs Normal tracks are different).
+                ALOGV("sleeping: ChannelID:%d  SampleID:%d", mChannelID, mSample->sampleID());
+                std::this_thread::sleep_for(std::chrono::milliseconds(20));
+            }
+        }
         mAudioTrack->stop();
         mPrevSampleID = mSample->sampleID();
         mSample.clear();
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d74103..01e4faa 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -61,7 +61,7 @@
     audio_channel_mask_t channelMask() { return mChannelMask; }
     size_t size() { return mSize; }
     int state() { return mState; }
-    uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
+    uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
     status_t doLoad();
     void startLoad() { mState = LOADING; }
     sp<IMemory> getIMemory() { return mData; }
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 338ec12..6b03e4d 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -17,7 +17,7 @@
 java_sdk_library {
     name: "com.android.mediadrm.signer",
     srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
     api_packages: ["com.android.mediadrm.signer"],
-    srcs_lib: "framework-minus-apex",
-    srcs_lib_whitelist_pkgs: ["android.media"],
 }
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 58317ed..a0d2050 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -40,6 +40,8 @@
         "libandroid_runtime",
     ],
 
+    version_script: "libamidi.map.txt",
+
     export_include_dirs: ["include"],
 }
 
diff --git a/media/tests/audiotests/shared_mem_test.cpp b/media/tests/audiotests/shared_mem_test.cpp
index 2f57499..d586b6a 100644
--- a/media/tests/audiotests/shared_mem_test.cpp
+++ b/media/tests/audiotests/shared_mem_test.cpp
@@ -92,7 +92,7 @@
 
         iMem = heap->allocate(BUF_SZ*sizeof(short));
 
-        p = static_cast<uint8_t*>(iMem->pointer());
+        p = static_cast<uint8_t*>(iMem->unsecurePointer());
         memcpy(p, smpBuf, BUF_SZ*sizeof(short));
 
         sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
diff --git a/mime/Android.bp b/mime/Android.bp
deleted file mode 100644
index 9303755..0000000
--- a/mime/Android.bp
+++ /dev/null
@@ -1,43 +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.
-
-java_library {
-    name: "mimemap",
-    visibility: [
-        "//cts/tests/tests/mimemap:__subpackages__",
-        "//frameworks/base:__subpackages__",
-    ],
-
-    srcs: [
-        "java/android/content/type/MimeMapImpl.java",
-    ],
-
-    java_resources: [
-        ":debian.mime.types",
-        ":android.mime.types",
-    ],
-
-    sdk_version: "core_platform",
-}
-
-filegroup {
-    name: "android.mime.types",
-    visibility: [
-        "//visibility:private",
-    ],
-    path: "java-res/",
-    srcs: [
-        "java-res/android.mime.types",
-    ],
-}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
deleted file mode 100644
index 1ca912e..0000000
--- a/mime/java-res/android.mime.types
+++ /dev/null
@@ -1,146 +0,0 @@
-
-###############################################################################
-#
-# Android-specific MIME type <-> extension mappings
-#
-# Each line below defines an mapping from one MIME type to the first of the
-# listed extensions, and from listed extension back to the MIME type.
-# A mapping overrides any previous mapping _from_ that same MIME type or
-# extension (put() semantics), unless that MIME type / extension is prefixed with '?'
-# (putIfAbsent() semantics).
-#
-#
-###############################################################################
-#
-# EXAMPLES
-#
-# A line of the form:
-#
-#    ?mime ext1 ?ext2 ext3
-#
-# affects the current mappings along the lines of the following pseudo code:
-#
-#    mimeToExt.putIfAbsent("mime", "ext1");
-#    extToMime.put("ext1", "mime");
-#    extToMime.putIfAbsent("ext2", "mime");
-#    extToMime.put("ext3", "mime");
-#
-# The line:
-#
-#     ?text/plain txt
-#
-# leaves any earlier mapping for "text/plain" untouched, or maps that MIME type
-# to the file extension ".txt" if there is no earlier mapping. The line also
-# sets the mapping from file extension ".txt" to be the MIME type "text/plain",
-# regardless of whether a previous mapping existed.
-#
-###############################################################################
-
-
-# File extensions that Android wants to override to point to the given MIME type.
-#
-# After processing a line of the form:
-# ?<mimeType> <extension1> <extension2>
-# If <mimeType> was not already mapped to an extension then it will be
-# mapped to <extension1>.
-# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
-
-?application/epub+zip epub
-?application/pkix-cert cer
-?application/rss+xml rss
-?application/vnd.android.ota ota
-?application/vnd.apple.mpegurl m3u8
-?application/vnd.ms-pki.stl stl
-?application/vnd.ms-powerpoint pot
-?application/vnd.ms-wpl wpl
-?application/vnd.stardivision.impress sdp
-?application/vnd.stardivision.writer vor
-?application/vnd.youtube.yt yt
-?application/x-android-drm-fl fl
-?application/x-flac flac
-?application/x-font pcf
-?application/x-mpegurl m3u m3u8
-?application/x-pem-file pem
-?application/x-pkcs12 p12 pfx
-?application/x-webarchive webarchive
-?application/x-webarchive-xml webarchivexml
-?application/x-x509-server-cert crt
-?application/x-x509-user-cert crt
-
-?audio/3gpp 3gpp
-?audio/aac-adts aac
-?audio/imelody imy
-?audio/midi rtttl xmf
-?audio/mobile-xmf mxmf
-?audio/mp4 m4a
-?audio/mpegurl m3u
-?audio/sp-midi smf
-?audio/x-matroska mka
-?audio/x-pn-realaudio ra
-
-?image/bmp bmp
-?image/heic heic
-?image/heic-sequence heics
-?image/heif heif hif
-?image/heif-sequence heifs
-?image/ico cur
-?image/webp webp
-?image/x-adobe-dng dng
-?image/x-fuji-raf raf
-?image/x-icon ico
-?image/x-nikon-nrw nrw
-?image/x-panasonic-rw2 rw2
-?image/x-pentax-pef pef
-?image/x-samsung-srw srw
-?image/x-sony-arw arw
-
-?text/comma-separated-values csv
-?text/plain diff po
-?text/rtf rtf
-?text/text phps
-?text/xml xml
-?text/x-vcard vcf
-
-?video/3gpp2 3gpp2 3g2
-?video/3gpp 3gpp
-?video/avi avi
-?video/m4v m4v
-?video/mp2p mpeg
-?video/mp2t m2ts mts
-?video/mp2ts ts
-?video/vnd.youtube.yt yt
-?video/x-webex wrf
-
-# Optional additions that should not override any previous mapping.
-
-?application/x-wifi-config ?xml
-
-# Special cases where Android has a strong opinion about mappings, so we
-# define them very last and make them override in both directions (no "?").
-#
-# Lines here are of the form:
-# <mimeType> <extension1> <extension2> ...
-#
-# After processing each line,
-#   <mimeType> is mapped to <extension1>
-#   <extension1>, <extension2>, ... are all mapped to <mimeType>
-# This overrides any mappings for this <mimeType> / for these extensions
-# that may have been defined earlier.
-
-application/pgp-signature pgp
-application/x-x509-ca-cert crt
-audio/aac aac
-audio/basic snd
-audio/flac flac
-audio/midi rtx
-audio/mpeg mp3 m4a m4r
-audio/x-mpegurl m3u m3u8
-image/jpeg jpg
-image/x-ms-bmp bmp
-text/plain txt
-text/x-c++hdr hpp
-text/x-c++src cpp
-video/3gpp 3gpp
-video/mpeg mpeg
-video/quicktime mov
-video/x-matroska mkv
diff --git a/mime/java/android/content/type/MimeMapImpl.java b/mime/java/android/content/type/MimeMapImpl.java
deleted file mode 100644
index c904ea3..0000000
--- a/mime/java/android/content/type/MimeMapImpl.java
+++ /dev/null
@@ -1,194 +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 android.content.type;
-
-import libcore.net.MimeMap;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * Default implementation of {@link MimeMap}, a bidirectional mapping between
- * MIME types and file extensions.
- *
- * This default mapping is loaded from data files that start with some mappings
- * recognized by IANA plus some custom extensions and overrides.
- *
- * @hide
- */
-public class MimeMapImpl extends MimeMap {
-
-    /**
-     * Creates and returns a new {@link MimeMapImpl} instance that implements.
-     * Android's default mapping between MIME types and extensions.
-     */
-    public static MimeMapImpl createDefaultInstance() {
-        return parseFromResources("/mime.types", "/android.mime.types");
-    }
-
-    private static final Pattern SPLIT_PATTERN = Pattern.compile("\\s+");
-
-    /**
-     * Note: These maps only contain lowercase keys/values, regarded as the
-     * {@link #toLowerCase(String) canonical form}.
-     *
-     * <p>This is the case for both extensions and MIME types. The mime.types
-     * data file contains examples of mixed-case MIME types, but some applications
-     * use the lowercase version of these same types. RFC 2045 section 2 states
-     * that MIME types are case insensitive.
-     */
-    private final Map<String, String> mMimeTypeToExtension;
-    private final Map<String, String> mExtensionToMimeType;
-
-    public MimeMapImpl(Map<String, String> mimeTypeToExtension,
-            Map<String, String> extensionToMimeType) {
-        this.mMimeTypeToExtension = new HashMap<>(mimeTypeToExtension);
-        for (Map.Entry<String, String> entry : mimeTypeToExtension.entrySet()) {
-            checkValidMimeType(entry.getKey());
-            checkValidExtension(entry.getValue());
-        }
-        this.mExtensionToMimeType = new HashMap<>(extensionToMimeType);
-        for (Map.Entry<String, String> entry : extensionToMimeType.entrySet()) {
-            checkValidExtension(entry.getKey());
-            checkValidMimeType(entry.getValue());
-        }
-    }
-
-    private static void checkValidMimeType(String s) {
-        if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) {
-            throw new IllegalArgumentException("Invalid MIME type: " + s);
-        }
-    }
-
-    private static void checkValidExtension(String s) {
-        if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) {
-            throw new IllegalArgumentException("Invalid extension: " + s);
-        }
-    }
-
-    static MimeMapImpl parseFromResources(String... resourceNames) {
-        Map<String, String> mimeTypeToExtension = new HashMap<>();
-        Map<String, String> extensionToMimeType = new HashMap<>();
-        for (String resourceName : resourceNames) {
-            parseTypes(mimeTypeToExtension, extensionToMimeType, resourceName);
-        }
-        return new MimeMapImpl(mimeTypeToExtension, extensionToMimeType);
-    }
-
-    /**
-     * An element of a *mime.types file: A MIME type or an extension, with an optional
-     * prefix of "?" (if not overriding an earlier value).
-     */
-    private static class Element {
-        public final boolean keepExisting;
-        public final String s;
-
-        Element(boolean keepExisting, String value) {
-            this.keepExisting = keepExisting;
-            this.s = toLowerCase(value);
-            if (value.isEmpty()) {
-                throw new IllegalArgumentException();
-            }
-        }
-
-        public String toString() {
-            return keepExisting ? ("?" + s) : s;
-        }
-    }
-
-    private static String maybePut(Map<String, String> map, Element keyElement, String value) {
-        if (keyElement.keepExisting) {
-            return map.putIfAbsent(keyElement.s, value);
-        } else {
-            return map.put(keyElement.s, value);
-        }
-    }
-
-    private static void parseTypes(Map<String, String> mimeTypeToExtension,
-            Map<String, String> extensionToMimeType, String resource) {
-        try (BufferedReader r = new BufferedReader(
-                new InputStreamReader(MimeMapImpl.class.getResourceAsStream(resource)))) {
-            String line;
-            while ((line = r.readLine()) != null) {
-                int commentPos = line.indexOf('#');
-                if (commentPos >= 0) {
-                    line = line.substring(0, commentPos);
-                }
-                line = line.trim();
-                // The first time a MIME type is encountered it is mapped to the first extension
-                // listed in its line. The first time an extension is encountered it is mapped
-                // to the MIME type.
-                //
-                // When encountering a previously seen MIME type or extension, then by default
-                // the later ones override earlier mappings (put() semantics); however if a MIME
-                // type or extension is prefixed with '?' then any earlier mapping _from_ that
-                // MIME type / extension is kept (putIfAbsent() semantics).
-                final String[] split = SPLIT_PATTERN.split(line);
-                if (split.length <= 1) {
-                    // Need mimeType + at least one extension to make a mapping.
-                    // "mime.types" files may also contain lines with just a mimeType without
-                    // an extension but we skip them as they provide no mapping info.
-                    continue;
-                }
-                List<Element> lineElements = new ArrayList<>(split.length);
-                for (String s : split) {
-                    boolean keepExisting = s.startsWith("?");
-                    if (keepExisting) {
-                        s = s.substring(1);
-                    }
-                    if (s.isEmpty()) {
-                        throw new IllegalArgumentException("Invalid entry in '" + line + "'");
-                    }
-                    lineElements.add(new Element(keepExisting, s));
-                }
-
-                // MIME type -> first extension (one mapping)
-                // This will override any earlier mapping from this MIME type to another
-                // extension, unless this MIME type was prefixed with '?'.
-                Element mimeElement = lineElements.get(0);
-                List<Element> extensionElements = lineElements.subList(1, lineElements.size());
-                String firstExtension = extensionElements.get(0).s;
-                maybePut(mimeTypeToExtension, mimeElement, firstExtension);
-
-                // extension -> MIME type (one or more mappings).
-                // This will override any earlier mapping from this extension to another
-                // MIME type, unless this extension was prefixed with '?'.
-                for (Element extensionElement : extensionElements) {
-                    maybePut(extensionToMimeType, extensionElement, mimeElement.s);
-                }
-            }
-        } catch (IOException | RuntimeException e) {
-            throw new RuntimeException("Failed to parse " + resource, e);
-        }
-    }
-
-    @Override
-    protected String guessExtensionFromLowerCaseMimeType(String mimeType) {
-        return mMimeTypeToExtension.get(mimeType);
-    }
-
-    @Override
-    protected String guessMimeTypeFromLowerCaseExtension(String extension) {
-        return mExtensionToMimeType.get(extension);
-    }
-}
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
new file mode 100644
index 0000000..9bcd677
--- /dev/null
+++ b/packages/BackupEncryption/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+
+android_app {
+    name: "BackupEncryption",
+    srcs: ["src/**/*.java"],
+    libs: ["backup-encryption-protos"],
+    optimize: { enabled: false },
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
+
+java_library {
+    name: "backup-encryption-protos",
+    proto: { type: "nano" },
+    srcs: ["proto/**/*.proto"],
+}
diff --git a/packages/BackupEncryption/AndroidManifest.xml b/packages/BackupEncryption/AndroidManifest.xml
new file mode 100644
index 0000000..a705df5
--- /dev/null
+++ b/packages/BackupEncryption/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.backup.encryption"
+    android:sharedUserId="android.uid.system" >
+
+    <application android:allowBackup="false" />
+</manifest>
diff --git a/packages/BackupEncryption/proguard.flags b/packages/BackupEncryption/proguard.flags
new file mode 100644
index 0000000..851ce8c
--- /dev/null
+++ b/packages/BackupEncryption/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.server.backup.encryption
diff --git a/packages/BackupEncryption/proto/backup_chunks_metadata.proto b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
new file mode 100644
index 0000000..2fdedbf
--- /dev/null
+++ b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "ChunksMetadataProto";
+
+// Cipher type with which the chunks are encrypted. For now we only support AES/GCM/NoPadding, but
+// this is for backwards-compatibility in case we need to change the default Cipher in the future.
+enum CipherType {
+    UNKNOWN_CIPHER_TYPE = 0;
+    // Chunk is prefixed with a 12-byte nonce. The tag length is 16 bytes.
+    AES_256_GCM = 1;
+}
+
+// Checksum type with which the plaintext is verified.
+enum ChecksumType {
+    UNKNOWN_CHECKSUM_TYPE = 0;
+    SHA_256 = 1;
+}
+
+enum ChunkOrderingType {
+    CHUNK_ORDERING_TYPE_UNSPECIFIED = 0;
+    // The chunk ordering contains a list of the start position of each chunk in the encrypted file,
+    // ordered as in the plaintext file. This allows us to recreate the original plaintext file
+    // during decryption. We use this mode for full backups where the order of the data in the file
+    // is important.
+    EXPLICIT_STARTS = 1;
+    // The chunk ordering does not contain any start positions, and instead each encrypted chunk in
+    // the backup file is prefixed with its length. This allows us to decrypt each chunk but does
+    // not give any information about the order. However, we use this mode for key value backups
+    // where the order does not matter.
+    INLINE_LENGTHS = 2;
+}
+
+// Chunk entry (for local state)
+message Chunk {
+    // SHA-256 MAC of the plaintext of the chunk
+    optional bytes hash = 1;
+    // Number of bytes in encrypted chunk
+    optional int32 length = 2;
+}
+
+// List of the chunks in the blob, along with the length of each chunk. From this is it possible to
+// extract individual chunks. (i.e., start position is equal to the sum of the lengths of all
+// preceding chunks.)
+//
+// This is local state stored on the device. It is never sent to the backup server. See
+// ChunkOrdering for how the device restores the chunks in the correct order.
+// Next tag : 6
+message ChunkListing {
+    repeated Chunk chunks = 1;
+
+    // Cipher algorithm with which the chunks are encrypted.
+    optional CipherType cipher_type = 2;
+
+    // Defines the type of chunk order used to encode the backup file on the server, so that we can
+    // consistently use the same type between backups. If unspecified this backup file was created
+    // before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
+    optional ChunkOrderingType chunk_ordering_type = 5;
+
+    // The document ID returned from Scotty server after uploading the blob associated with this
+    // listing. This needs to be sent when uploading new diff scripts.
+    optional string document_id = 3;
+
+    // Fingerprint mixer salt used for content defined chunking. This is randomly generated for each
+    // package during the initial non-incremental backup and reused for incremental backups.
+    optional bytes fingerprint_mixer_salt = 4;
+}
+
+// Ordering information about plaintext and checksum. This is used on restore to reconstruct the
+// blob in its correct order. (The chunk order is randomized so as to give the server less
+// information about which parts of the backup are changing over time.) This proto is encrypted
+// before being uploaded to the server, with a key unknown to the server.
+message ChunkOrdering {
+    // For backups where ChunksMetadata#chunk_ordering_type = EXPLICIT STARTS:
+    // Ordered start positions of chunks. i.e., the file is the chunk starting at this position,
+    // followed by the chunk starting at this position, followed by ... etc. You can compute the
+    // lengths of the chunks by sorting this list then looking at the start position of the next
+    // chunk after the chunk you care about. This is guaranteed to work as all chunks are
+    // represented in this list.
+    //
+    // For backups where ChunksMetadata#chunk_ordering_type = INLINE_LENGTHS:
+    // This field is unused. See ChunkOrderingType#INLINE_LENGTHS.
+    repeated int32 starts = 1 [packed = true];
+
+    // Checksum of plaintext content. (i.e., in correct order.)
+    //
+    // Each chunk also has a MAC, as generated by GCM, so this is NOT Mac-then-Encrypt, which has
+    // security implications. This is an additional checksum to verify that once the chunks have
+    // been reordered, that the file matches the expected plaintext. This prevents the device
+    // restoring garbage data in case of a mismatch between the ChunkOrdering and the backup blob.
+    optional bytes checksum = 2;
+}
+
+// Additional metadata about a backup blob that needs to be synced to the server. This is used on
+// restore to reconstruct the blob in its correct order. (The chunk order is randomized so as to
+// give the server less information about which parts of the backup are changing over time.) This
+// data structure is only ever uploaded to the server encrypted with a key unknown to the server.
+// Next tag : 6
+message ChunksMetadata {
+    // Cipher algorithm with which the chunk listing and chunks are encrypted.
+    optional CipherType cipher_type = 1;
+
+    // Defines the type of chunk order this metadata contains. If unspecified this backup file was
+    // created before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS.
+    optional ChunkOrderingType chunk_ordering_type = 5
+    [default = CHUNK_ORDERING_TYPE_UNSPECIFIED];
+
+    // Encrypted bytes of ChunkOrdering
+    optional bytes chunk_ordering = 2;
+
+    // The type of algorithm used for the checksum of the plaintext. (See ChunkOrdering.) This is
+    // for forwards compatibility in case we change the algorithm in the future. For now, always
+    // SHA-256.
+    optional ChecksumType checksum_type = 3;
+
+    // This used to be the plaintext tertiary key. No longer used.
+    reserved 4;
+}
\ No newline at end of file
diff --git a/packages/BackupEncryption/proto/key_value_listing.proto b/packages/BackupEncryption/proto/key_value_listing.proto
new file mode 100644
index 0000000..001e697
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_listing.proto
@@ -0,0 +1,40 @@
+/*
+ * 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
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValueListingProto";
+
+// An entry of a key-value pair.
+message KeyValueEntry {
+  // Plaintext key of the key-value pair.
+  optional string key = 1;
+  // SHA-256 MAC of the plaintext of the chunk containing the pair
+  optional bytes hash = 2;
+}
+
+// Describes the key/value pairs currently in the backup blob, mapping from the
+// plaintext key to the hash of the chunk containing the pair.
+//
+// This is local state stored on the device. It is never sent to the
+// backup server. See ChunkOrdering for how the device restores the
+// key-value pairs in the correct order.
+message KeyValueListing {
+  repeated KeyValueEntry entries = 1;
+}
diff --git a/packages/BackupEncryption/proto/key_value_pair.proto b/packages/BackupEncryption/proto/key_value_pair.proto
new file mode 100644
index 0000000..177fa30
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_pair.proto
@@ -0,0 +1,31 @@
+/*
+ * 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
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValuePairProto";
+
+// Serialized form of a key-value pair, when it is to be encrypted in a blob.
+// The backup blob for a key-value database consists of repeated encrypted
+// key-value pairs like this, in a randomized order. See ChunkOrdering for how
+// these are then reconstructed during a restore.
+message KeyValuePair {
+  optional string key = 1;
+  optional bytes value = 2;
+}
diff --git a/packages/BackupEncryption/proto/wrapped_key.proto b/packages/BackupEncryption/proto/wrapped_key.proto
new file mode 100644
index 0000000..817b7b4
--- /dev/null
+++ b/packages/BackupEncryption/proto/wrapped_key.proto
@@ -0,0 +1,52 @@
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "WrappedKeyProto";
+
+// Metadata associated with a tertiary key.
+message KeyMetadata {
+  // Type of Cipher algorithm the key is used for.
+  enum Type {
+    UNKNOWN = 0;
+    // No padding. Uses 12-byte nonce. Tag length 16 bytes.
+    AES_256_GCM = 1;
+  }
+
+  // What kind of Cipher algorithm the key is used for. We assume at the moment
+  // that this will always be AES_256_GCM and throw if this is not the case.
+  // Provided here for forwards compatibility in case at some point we need to
+  // change Cipher algorithm.
+  optional Type type = 1;
+}
+
+// An encrypted tertiary key.
+message WrappedKey {
+  // The Cipher with which the key was encrypted.
+  enum WrapAlgorithm {
+    UNKNOWN = 0;
+    // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes.
+    // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore.
+    // AndroidKeyStore requires that it generates the IV, and it generates a
+    // 16-byte IV for you. You CANNOT provide your own IV.
+    AES_256_GCM = 1;
+  }
+
+  // Cipher algorithm used to wrap the key. We assume at the moment that this
+  // is always AES_256_GC and throw if this is not the case. Provided here for
+  // forwards compatibility if at some point we need to change Cipher algorithm.
+  optional WrapAlgorithm wrap_algorithm = 1;
+
+  // The nonce used to initialize the Cipher in AES/256/GCM mode.
+  optional bytes nonce = 2;
+
+  // The encrypted bytes of the key material.
+  optional bytes key = 3;
+
+  // Associated key metadata.
+  optional KeyMetadata metadata = 4;
+
+  // Deprecated field; Do not use
+  reserved 5;
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
new file mode 100644
index 0000000..033f1b1
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.KeyStoreException;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * State about encrypted backups that needs to be remembered.
+ */
+public class CryptoSettings {
+
+    private static final String TAG = "CryptoSettings";
+
+    private static final String SHARED_PREFERENCES_NAME = "crypto_settings";
+
+    private static final String KEY_IS_INITIALIZED = "isInitialized";
+    private static final String KEY_ACTIVE_SECONDARY_ALIAS = "activeSecondary";
+    private static final String KEY_NEXT_SECONDARY_ALIAS = "nextSecondary";
+    private static final String SECONDARY_KEY_LAST_ROTATED_AT = "secondaryKeyLastRotatedAt";
+    private static final String[] SETTINGS_FOR_BACKUP = {
+        KEY_IS_INITIALIZED,
+        KEY_ACTIVE_SECONDARY_ALIAS,
+        KEY_NEXT_SECONDARY_ALIAS,
+        SECONDARY_KEY_LAST_ROTATED_AT
+    };
+
+    private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD =
+            TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS);
+
+    private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION =
+            "ancestral_secondary_key_version";
+
+    private final SharedPreferences mSharedPreferences;
+    private final Context mContext;
+
+    /**
+     * A new instance.
+     *
+     * @param context For looking up the {@link SharedPreferences}, for storing state.
+     * @return The instance.
+     */
+    public static CryptoSettings getInstance(Context context) {
+        // We need single process mode because CryptoSettings may be used from several processes
+        // simultaneously.
+        SharedPreferences sharedPreferences =
+                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+        return new CryptoSettings(sharedPreferences, context);
+    }
+
+    /**
+     * A new instance using {@link SharedPreferences} in the default mode.
+     *
+     * <p>This will not work across multiple processes but will work in tests.
+     */
+    @VisibleForTesting
+    public static CryptoSettings getInstanceForTesting(Context context) {
+        SharedPreferences sharedPreferences =
+                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+        return new CryptoSettings(sharedPreferences, context);
+    }
+
+    private CryptoSettings(SharedPreferences sharedPreferences, Context context) {
+        mSharedPreferences = checkNotNull(sharedPreferences);
+        mContext = checkNotNull(context);
+    }
+
+    /**
+     * The alias of the current active secondary key. This should be used to retrieve the key from
+     * AndroidKeyStore.
+     */
+    public Optional<String> getActiveSecondaryKeyAlias() {
+        return getStringInSharedPrefs(KEY_ACTIVE_SECONDARY_ALIAS);
+    }
+
+    /**
+     * The alias of the secondary key to which the client is rotating. The rotation is not
+     * immediate, which is why this setting is needed. Once the next key is created, it can take up
+     * to 72 hours potentially (or longer if the user has no network) for the next key to be synced
+     * with the keystore. Only after that has happened does the client attempt to re-wrap all
+     * tertiary keys and commit the rotation.
+     */
+    public Optional<String> getNextSecondaryKeyAlias() {
+        return getStringInSharedPrefs(KEY_NEXT_SECONDARY_ALIAS);
+    }
+
+    /**
+     * If the settings have been initialized.
+     */
+    public boolean getIsInitialized() {
+        return mSharedPreferences.getBoolean(KEY_IS_INITIALIZED, false);
+    }
+
+    /**
+     * Sets the alias of the currently active secondary key.
+     *
+     * @param activeAlias The alias, as in AndroidKeyStore.
+     * @throws IllegalArgumentException if the alias is not in the user's keystore.
+     */
+    public void setActiveSecondaryKeyAlias(String activeAlias) throws IllegalArgumentException {
+        assertIsValidAlias(activeAlias);
+        mSharedPreferences.edit().putString(KEY_ACTIVE_SECONDARY_ALIAS, activeAlias).apply();
+    }
+
+    /**
+     * Sets the alias of the secondary key to which the client is rotating.
+     *
+     * @param nextAlias The alias, as in AndroidKeyStore.
+     * @throws KeyStoreException if unable to check whether alias is valid in the keystore.
+     * @throws IllegalArgumentException if the alias is not in the user's keystore.
+     */
+    public void setNextSecondaryAlias(String nextAlias) throws IllegalArgumentException {
+        assertIsValidAlias(nextAlias);
+        mSharedPreferences.edit().putString(KEY_NEXT_SECONDARY_ALIAS, nextAlias).apply();
+    }
+
+    /**
+     * Unsets the alias of the key to which the client is rotating. This is generally performed once
+     * a rotation is complete.
+     */
+    public void removeNextSecondaryKeyAlias() {
+        mSharedPreferences.edit().remove(KEY_NEXT_SECONDARY_ALIAS).apply();
+    }
+
+    /**
+     * Sets the timestamp of when the secondary key was last rotated.
+     *
+     * @param timestamp The timestamp to set.
+     */
+    public void setSecondaryLastRotated(long timestamp) {
+        mSharedPreferences.edit().putLong(SECONDARY_KEY_LAST_ROTATED_AT, timestamp).apply();
+    }
+
+    /**
+     * Returns a timestamp of when the secondary key was last rotated.
+     *
+     * @return The timestamp.
+     */
+    public Optional<Long> getSecondaryLastRotated() {
+        if (!mSharedPreferences.contains(SECONDARY_KEY_LAST_ROTATED_AT)) {
+            return Optional.empty();
+        }
+        return Optional.of(mSharedPreferences.getLong(SECONDARY_KEY_LAST_ROTATED_AT, -1));
+    }
+
+    /**
+     * Sets the settings to have been initialized. (Otherwise loading should try to initialize
+     * again.)
+     */
+    private void setIsInitialized() {
+        mSharedPreferences.edit().putBoolean(KEY_IS_INITIALIZED, true).apply();
+    }
+
+    /**
+     * Initializes with the given key alias.
+     *
+     * @param alias The secondary key alias to be set as active.
+     * @throws IllegalArgumentException if the alias does not reference a valid key.
+     * @throws IllegalStateException if attempting to initialize an already initialized settings.
+     */
+    public void initializeWithKeyAlias(String alias) throws IllegalArgumentException {
+        checkState(
+                !getIsInitialized(), "Attempting to initialize an already initialized settings.");
+        setActiveSecondaryKeyAlias(alias);
+        setIsInitialized();
+    }
+
+    /** Returns the secondary key version of the encrypted backup set to restore from (if set). */
+    public Optional<String> getAncestralSecondaryKeyVersion() {
+        return Optional.ofNullable(
+                mSharedPreferences.getString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, null));
+    }
+
+    /** Sets the secondary key version of the encrypted backup set to restore from. */
+    public void setAncestralSecondaryKeyVersion(String ancestralSecondaryKeyVersion) {
+        mSharedPreferences
+                .edit()
+                .putString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, ancestralSecondaryKeyVersion)
+                .apply();
+    }
+
+    /** The number of milliseconds between secondary key rotation */
+    public long backupSecondaryKeyRotationIntervalMs() {
+        return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD;
+    }
+
+    /** Deletes all crypto settings related to backup (as opposed to restore). */
+    public void clearAllSettingsForBackup() {
+        Editor sharedPrefsEditor = mSharedPreferences.edit();
+        for (String backupSettingKey : SETTINGS_FOR_BACKUP) {
+            sharedPrefsEditor.remove(backupSettingKey);
+        }
+        sharedPrefsEditor.apply();
+
+        Slog.d(TAG, "Cleared crypto settings for backup");
+    }
+
+    /**
+     * Throws {@link IllegalArgumentException} if the alias does not refer to a key that is in
+     * the {@link RecoveryController}.
+     */
+    private void assertIsValidAlias(String alias) throws IllegalArgumentException {
+        try {
+            if (!RecoveryController.getInstance(mContext).getAliases().contains(alias)) {
+                throw new IllegalArgumentException(alias + " is not in RecoveryController");
+            }
+        } catch (InternalRecoveryServiceException e) {
+            throw new IllegalArgumentException("Problem accessing recovery service", e);
+        }
+    }
+
+    private Optional<String> getStringInSharedPrefs(String key) {
+        return Optional.ofNullable(mSharedPreferences.getString(key, null));
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java
new file mode 100644
index 0000000..1630eb8
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Base64;
+
+/**
+ * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key.
+ *
+ * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link
+ * #hashCode()}.
+ */
+public class ChunkHash implements Comparable<ChunkHash> {
+    /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */
+    public static final int HASH_LENGTH_BYTES = 256 / 8;
+
+    private static final int UNSIGNED_MASK = 0xFF;
+
+    private final byte[] mHash;
+
+    /** Constructs a new instance which wraps the given SHA-256 hash bytes. */
+    public ChunkHash(byte[] hash) {
+        Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits");
+        mHash = hash;
+    }
+
+    public byte[] getHash() {
+        return mHash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ChunkHash)) {
+            return false;
+        }
+
+        ChunkHash chunkHash = (ChunkHash) o;
+        return Arrays.equals(mHash, chunkHash.mHash);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mHash);
+    }
+
+    @Override
+    public int compareTo(ChunkHash other) {
+        return lexicographicalCompareUnsignedBytes(getHash(), other.getHash());
+    }
+
+    @Override
+    public String toString() {
+        return Base64.getEncoder().encodeToString(mHash);
+    }
+
+    private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) {
+        int minLength = Math.min(left.length, right.length);
+        for (int i = 0; i < minLength; i++) {
+            int result = toInt(left[i]) - toInt(right[i]);
+            if (result != 0) {
+                return result;
+            }
+        }
+        return left.length - right.length;
+    }
+
+    private static int toInt(byte value) {
+        return value & UNSIGNED_MASK;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
new file mode 100644
index 0000000..51d7d53
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import android.annotation.Nullable;
+
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Chunk listing in a format optimized for quick look up of chunks via their hash keys. This is
+ * useful when building an incremental backup. After a chunk has been produced, the algorithm can
+ * quickly look up whether the chunk existed in the previous backup by checking this chunk listing.
+ * It can then tell the server to use that chunk, through telling it the position and length of the
+ * chunk in the previous backup's blob.
+ */
+public class ChunkListingMap {
+
+    private final Map<ChunkHash, Entry> mChunksByHash;
+
+    /** Construct a map from a {@link ChunksMetadataProto.ChunkListing} protobuf */
+    public static ChunkListingMap fromProto(ChunksMetadataProto.ChunkListing chunkListingProto) {
+        Map<ChunkHash, Entry> entries = new HashMap<>();
+
+        long start = 0;
+
+        for (ChunksMetadataProto.Chunk chunk : chunkListingProto.chunks) {
+            entries.put(new ChunkHash(chunk.hash), new Entry(start, chunk.length));
+            start += chunk.length;
+        }
+
+        return new ChunkListingMap(entries);
+    }
+
+    private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
+        // This is only called from the {@link #fromProto} method, so we don't
+        // need to take a copy.
+        this.mChunksByHash = chunksByHash;
+    }
+
+    /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */
+    public boolean hasChunk(ChunkHash hash) {
+        return mChunksByHash.containsKey(hash);
+    }
+
+    /**
+     * Returns the entry for the chunk with the given hash.
+     *
+     * @param hash The SHA-256 MAC of the plaintext of the chunk.
+     * @return The entry, containing position and length of the chunk in the backup blob, or null if
+     *     it does not exist.
+     */
+    @Nullable
+    public Entry getChunkEntry(ChunkHash hash) {
+        return mChunksByHash.get(hash);
+    }
+
+    /** Information about a chunk entry in a backup blob - i.e., its position and length. */
+    public static final class Entry {
+        private final int mLength;
+        private final long mStart;
+
+        private Entry(long start, int length) {
+            mLength = length;
+            mStart = start;
+        }
+
+        /** Returns the length of the chunk in bytes. */
+        public int getLength() {
+            return mLength;
+        }
+
+        /** Returns the start position of the chunk in the backup blob, in bytes. */
+        public long getStart() {
+            return mStart;
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
new file mode 100644
index 0000000..9cda339
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.EXPLICIT_STARTS;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.INLINE_LENGTHS;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** IntDef corresponding to the ChunkOrderingType enum in the ChunksMetadataProto protobuf. */
+@IntDef({CHUNK_ORDERING_TYPE_UNSPECIFIED, EXPLICIT_STARTS, INLINE_LENGTHS})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ChunkOrderingType {}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
new file mode 100644
index 0000000..edf1b9a
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import java.util.Arrays;
+
+/**
+ * Holds the bytes of an encrypted {@link ChunksMetadataProto.ChunkOrdering}.
+ *
+ * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
+ * encryptedChunkOrdering() to getBytes().
+ */
+public class EncryptedChunkOrdering {
+    /**
+     * Constructs a new object holding the given bytes of an encrypted {@link
+     * ChunksMetadataProto.ChunkOrdering}.
+     *
+     * <p>Note that this just holds an ordering which is already encrypted, it does not encrypt the
+     * ordering.
+     */
+    public static EncryptedChunkOrdering create(byte[] encryptedChunkOrdering) {
+        return new EncryptedChunkOrdering(encryptedChunkOrdering);
+    }
+
+    private final byte[] mEncryptedChunkOrdering;
+
+    /** Get the encrypted chunk ordering */
+    public byte[] encryptedChunkOrdering() {
+        return mEncryptedChunkOrdering;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof EncryptedChunkOrdering)) {
+            return false;
+        }
+
+        EncryptedChunkOrdering encryptedChunkOrdering = (EncryptedChunkOrdering) o;
+        return Arrays.equals(
+                mEncryptedChunkOrdering, encryptedChunkOrdering.mEncryptedChunkOrdering);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mEncryptedChunkOrdering);
+    }
+
+    private EncryptedChunkOrdering(byte[] encryptedChunkOrdering) {
+        mEncryptedChunkOrdering = encryptedChunkOrdering;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java
new file mode 100644
index 0000000..3d3fb55
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunk.ChunkListingMap;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Writes batches of {@link EncryptedChunk} to a diff script, and generates the associated {@link
+ * ChunksMetadataProto.ChunkListing} and {@link ChunksMetadataProto.ChunkOrdering}.
+ */
+public class BackupFileBuilder {
+    private static final String TAG = "BackupFileBuilder";
+
+    private static final int BYTES_PER_KILOBYTE = 1024;
+
+    private final BackupWriter mBackupWriter;
+    private final EncryptedChunkEncoder mEncryptedChunkEncoder;
+    private final ChunkListingMap mOldChunkListing;
+    private final ChunksMetadataProto.ChunkListing mNewChunkListing;
+    private final ChunksMetadataProto.ChunkOrdering mChunkOrdering;
+    private final List<ChunksMetadataProto.Chunk> mKnownChunks = new ArrayList<>();
+    private final List<Integer> mKnownStarts = new ArrayList<>();
+    private final Map<ChunkHash, Long> mChunkStartPositions;
+
+    private long mNewChunksSizeBytes;
+    private boolean mFinished;
+
+    /**
+     * Constructs a new instance which writes raw data to the given {@link OutputStream}, without
+     * generating a diff.
+     *
+     * <p>This class never closes the output stream.
+     */
+    public static BackupFileBuilder createForNonIncremental(OutputStream outputStream) {
+        return new BackupFileBuilder(
+                new RawBackupWriter(outputStream), new ChunksMetadataProto.ChunkListing());
+    }
+
+    /**
+     * Constructs a new instance which writes a diff script to the given {@link OutputStream} using
+     * a {@link SingleStreamDiffScriptWriter}.
+     *
+     * <p>This class never closes the output stream.
+     *
+     * @param oldChunkListing against which the diff will be generated.
+     */
+    public static BackupFileBuilder createForIncremental(
+            OutputStream outputStream, ChunksMetadataProto.ChunkListing oldChunkListing) {
+        return new BackupFileBuilder(
+                DiffScriptBackupWriter.newInstance(outputStream), oldChunkListing);
+    }
+
+    private BackupFileBuilder(
+            BackupWriter backupWriter, ChunksMetadataProto.ChunkListing oldChunkListing) {
+        this.mBackupWriter = backupWriter;
+        // TODO(b/77188289): Use InlineLengthsEncryptedChunkEncoder for key-value backups
+        this.mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder();
+        this.mOldChunkListing = ChunkListingMap.fromProto(oldChunkListing);
+
+        mNewChunkListing = new ChunksMetadataProto.ChunkListing();
+        mNewChunkListing.cipherType = ChunksMetadataProto.AES_256_GCM;
+        mNewChunkListing.chunkOrderingType = ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+
+        mChunkOrdering = new ChunksMetadataProto.ChunkOrdering();
+        mChunkStartPositions = new HashMap<>();
+    }
+
+    /**
+     * Writes the given chunks to the output stream, and adds them to the new chunk listing and
+     * chunk ordering.
+     *
+     * <p>Sorts the chunks in lexicographical order before writing.
+     *
+     * @param allChunks The hashes of all the chunks, in the order they appear in the plaintext.
+     * @param newChunks A map from hash to {@link EncryptedChunk} containing the new chunks not
+     *     present in the previous backup.
+     */
+    public void writeChunks(List<ChunkHash> allChunks, Map<ChunkHash, EncryptedChunk> newChunks)
+            throws IOException {
+        checkState(!mFinished, "Cannot write chunks after flushing.");
+
+        List<ChunkHash> sortedChunks = new ArrayList<>(allChunks);
+        Collections.sort(sortedChunks);
+        for (ChunkHash chunkHash : sortedChunks) {
+            // As we have already included this chunk in the backup file, don't add it again to
+            // deduplicate identical chunks.
+            if (!mChunkStartPositions.containsKey(chunkHash)) {
+                // getBytesWritten() gives us the start of the chunk.
+                mChunkStartPositions.put(chunkHash, mBackupWriter.getBytesWritten());
+
+                writeChunkToFileAndListing(chunkHash, newChunks);
+            }
+        }
+
+        long totalSizeKb = mBackupWriter.getBytesWritten() / BYTES_PER_KILOBYTE;
+        long newChunksSizeKb = mNewChunksSizeBytes / BYTES_PER_KILOBYTE;
+        Slog.d(
+                TAG,
+                "Total backup size: "
+                        + totalSizeKb
+                        + " kb, new chunks size: "
+                        + newChunksSizeKb
+                        + " kb");
+
+        for (ChunkHash chunkHash : allChunks) {
+            mKnownStarts.add(mChunkStartPositions.get(chunkHash).intValue());
+        }
+    }
+
+    /**
+     * Returns a new listing for all of the chunks written so far, setting the given fingerprint
+     * mixer salt (this overrides the {@link ChunksMetadataProto.ChunkListing#fingerprintMixerSalt}
+     * in the old {@link ChunksMetadataProto.ChunkListing} passed into the
+     * {@link #BackupFileBuilder).
+     */
+    public ChunksMetadataProto.ChunkListing getNewChunkListing(
+            @Nullable byte[] fingerprintMixerSalt) {
+        // TODO: b/141537803 Add check to ensure this is called only once per instance
+        mNewChunkListing.fingerprintMixerSalt =
+                fingerprintMixerSalt != null
+                        ? Arrays.copyOf(fingerprintMixerSalt, fingerprintMixerSalt.length)
+                        : new byte[0];
+        mNewChunkListing.chunks = mKnownChunks.toArray(new ChunksMetadataProto.Chunk[0]);
+        return mNewChunkListing;
+    }
+
+    /** Returns a new ordering for all of the chunks written so far, setting the given checksum. */
+    public ChunksMetadataProto.ChunkOrdering getNewChunkOrdering(byte[] checksum) {
+        // TODO: b/141537803 Add check to ensure this is called only once per instance
+        mChunkOrdering.starts = new int[mKnownStarts.size()];
+        for (int i = 0; i < mKnownStarts.size(); i++) {
+            mChunkOrdering.starts[i] = mKnownStarts.get(i).intValue();
+        }
+        mChunkOrdering.checksum = Arrays.copyOf(checksum, checksum.length);
+        return mChunkOrdering;
+    }
+
+    /**
+     * Finishes the backup file by writing the chunk metadata and metadata position.
+     *
+     * <p>Once this is called, calling {@link #writeChunks(List, Map)} will throw {@link
+     * IllegalStateException}.
+     */
+    public void finish(ChunksMetadataProto.ChunksMetadata metadata) throws IOException {
+        checkNotNull(metadata, "Metadata cannot be null");
+
+        long startOfMetadata = mBackupWriter.getBytesWritten();
+        mBackupWriter.writeBytes(ChunksMetadataProto.ChunksMetadata.toByteArray(metadata));
+        mBackupWriter.writeBytes(toByteArray(startOfMetadata));
+
+        mBackupWriter.flush();
+        mFinished = true;
+    }
+
+    /**
+     * Checks if the given chunk hash references an existing chunk or a new chunk, and adds this
+     * chunk to the backup file and new chunk listing.
+     */
+    private void writeChunkToFileAndListing(
+            ChunkHash chunkHash, Map<ChunkHash, EncryptedChunk> newChunks) throws IOException {
+        checkNotNull(chunkHash, "Hash cannot be null");
+
+        if (mOldChunkListing.hasChunk(chunkHash)) {
+            ChunkListingMap.Entry oldChunk = mOldChunkListing.getChunkEntry(chunkHash);
+            mBackupWriter.writeChunk(oldChunk.getStart(), oldChunk.getLength());
+
+            checkArgument(oldChunk.getLength() >= 0, "Chunk must have zero or positive length");
+            addChunk(chunkHash.getHash(), oldChunk.getLength());
+        } else if (newChunks.containsKey(chunkHash)) {
+            EncryptedChunk newChunk = newChunks.get(chunkHash);
+            mEncryptedChunkEncoder.writeChunkToWriter(mBackupWriter, newChunk);
+            int length = mEncryptedChunkEncoder.getEncodedLengthOfChunk(newChunk);
+            mNewChunksSizeBytes += length;
+
+            checkArgument(length >= 0, "Chunk must have zero or positive length");
+            addChunk(chunkHash.getHash(), length);
+        } else {
+            throw new IllegalArgumentException(
+                    "Chunk did not exist in old chunks or new chunks: " + chunkHash);
+        }
+    }
+
+    private void addChunk(byte[] chunkHash, int length) {
+        ChunksMetadataProto.Chunk chunk = new ChunksMetadataProto.Chunk();
+        chunk.hash = Arrays.copyOf(chunkHash, chunkHash.length);
+        chunk.length = length;
+        mKnownChunks.add(chunk);
+    }
+
+    private static byte[] toByteArray(long value) {
+        // Note that this code needs to stay compatible with GWT, which has known
+        // bugs when narrowing byte casts of long values occur.
+        byte[] result = new byte[8];
+        for (int i = 7; i >= 0; i--) {
+            result[i] = (byte) (value & 0xffL);
+            value >>= 8;
+        }
+        return result;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java
new file mode 100644
index 0000000..baa820c
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import java.io.IOException;
+
+/** Writes backup data either as a diff script or as raw data, determined by the implementation. */
+public interface BackupWriter {
+    /** Writes the given bytes to the output. */
+    void writeBytes(byte[] bytes) throws IOException;
+
+    /**
+     * Writes an existing chunk from the previous backup to the output.
+     *
+     * <p>Note: not all implementations support this method.
+     */
+    void writeChunk(long start, int length) throws IOException;
+
+    /** Returns the number of bytes written, included bytes copied from the old file. */
+    long getBytesWritten();
+
+    /**
+     * Indicates that no more bytes or chunks will be written.
+     *
+     * <p>After calling this, you may not call {@link #writeBytes(byte[])} or {@link
+     * #writeChunk(long, int)}
+     */
+    void flush() throws IOException;
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
new file mode 100644
index 0000000..48abc8c
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Encrypts chunks of a file using AES/GCM. */
+public class ChunkEncryptor {
+    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final int GCM_NONCE_LENGTH_BYTES = 12;
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+    private final SecretKey mSecretKey;
+    private final SecureRandom mSecureRandom;
+
+    /**
+     * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to
+     * generate nonces.
+     */
+    public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) {
+        this.mSecretKey = secretKey;
+        this.mSecureRandom = secureRandom;
+    }
+
+    /**
+     * Transforms {@code plaintext} into an {@link EncryptedChunk}.
+     *
+     * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk.
+     * @param plaintext Bytes to encrypt.
+     * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption.
+     * @throws IllegalBlockSizeException If the input data cannot be encrypted using
+     *     AES/GCM/NoPadding. This should never be the case.
+     */
+    public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext)
+            throws InvalidKeyException, IllegalBlockSizeException {
+        byte[] nonce = generateNonce();
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+            cipher.init(
+                    Cipher.ENCRYPT_MODE,
+                    mSecretKey,
+                    new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce));
+        } catch (NoSuchAlgorithmException
+                | NoSuchPaddingException
+                | InvalidAlgorithmParameterException e) {
+            // This can not happen - AES/GCM/NoPadding is supported.
+            throw new AssertionError(e);
+        }
+        byte[] encryptedBytes;
+        try {
+            encryptedBytes = cipher.doFinal(plaintext);
+        } catch (BadPaddingException e) {
+            // This can not happen - BadPaddingException can only be thrown in decrypt mode.
+            throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode.");
+        }
+
+        return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes);
+    }
+
+    private byte[] generateNonce() {
+        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+        mSecureRandom.nextBytes(nonce);
+        return nonce;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java
new file mode 100644
index 0000000..02d498c
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+/** Computes the SHA-256 HMAC of a chunk of bytes. */
+public class ChunkHasher {
+    private static final String MAC_ALGORITHM = "HmacSHA256";
+
+    private final SecretKey mSecretKey;
+
+    /** Constructs a new hasher which computes the HMAC using the given secret key. */
+    public ChunkHasher(SecretKey secretKey) {
+        this.mSecretKey = secretKey;
+    }
+
+    /** Returns the SHA-256 over the given bytes. */
+    public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException {
+        try {
+            Mac mac = Mac.getInstance(MAC_ALGORITHM);
+            mac.init(mSecretKey);
+            return new ChunkHash(mac.doFinal(plaintext));
+        } catch (NoSuchAlgorithmException e) {
+            // This can not happen - AES/GCM/NoPadding is available as part of the framework.
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java
new file mode 100644
index 0000000..c9a6293
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+
+/** Splits an input stream into chunks, which are to be encrypted separately. */
+public interface Chunker {
+    /**
+     * Splits the input stream into chunks.
+     *
+     * @param inputStream The input stream.
+     * @param chunkConsumer A function that processes each chunk as it is produced.
+     * @throws IOException If there is a problem reading the input stream.
+     * @throws GeneralSecurityException if the consumer function throws an error.
+     */
+    void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
+            throws IOException, GeneralSecurityException;
+
+    /** Function that consumes chunks. */
+    interface ChunkConsumer {
+        /**
+         * Invoked for each chunk.
+         *
+         * @param chunk Plaintext bytes of chunk.
+         * @throws GeneralSecurityException if there is an issue encrypting the chunk.
+         */
+        void accept(byte[] chunk) throws GeneralSecurityException;
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java
new file mode 100644
index 0000000..cde59fa
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A chunk of a file encrypted using AES/GCM.
+ *
+ * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
+ * encryptedBytes(), key() and nonce().
+ */
+public class EncryptedChunk {
+    public static final int KEY_LENGTH_BYTES = ChunkHash.HASH_LENGTH_BYTES;
+    public static final int NONCE_LENGTH_BYTES = 12;
+
+    /**
+     * Constructs a new instance with the given key, nonce, and encrypted bytes.
+     *
+     * @param key SHA-256 Hmac of the chunk plaintext.
+     * @param nonce Nonce with which the bytes of the chunk were encrypted.
+     * @param encryptedBytes Encrypted bytes of the chunk.
+     */
+    public static EncryptedChunk create(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
+        Preconditions.checkArgument(
+                nonce.length == NONCE_LENGTH_BYTES, "Nonce does not have the correct length.");
+        return new EncryptedChunk(key, nonce, encryptedBytes);
+    }
+
+    private ChunkHash mKey;
+    private byte[] mNonce;
+    private byte[] mEncryptedBytes;
+
+    private EncryptedChunk(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
+        mKey = key;
+        mNonce = nonce;
+        mEncryptedBytes = encryptedBytes;
+    }
+
+    /** The SHA-256 Hmac of the plaintext bytes of the chunk. */
+    public ChunkHash key() {
+        return mKey;
+    }
+
+    /** The nonce with which the chunk was encrypted. */
+    public byte[] nonce() {
+        return mNonce;
+    }
+
+    /** The encrypted bytes of the chunk. */
+    public byte[] encryptedBytes() {
+        return mEncryptedBytes;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof EncryptedChunk)) {
+            return false;
+        }
+
+        EncryptedChunk encryptedChunkOrdering = (EncryptedChunk) o;
+        return Arrays.equals(mEncryptedBytes, encryptedChunkOrdering.mEncryptedBytes)
+                && Arrays.equals(mNonce, encryptedChunkOrdering.mNonce)
+                && mKey.equals(encryptedChunkOrdering.mKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mKey, Arrays.hashCode(mNonce), Arrays.hashCode(mEncryptedBytes));
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
new file mode 100644
index 0000000..16beda3
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+
+import java.io.IOException;
+
+/** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */
+public interface EncryptedChunkEncoder {
+    /**
+     * Encodes the given chunk and asks the writer to write it.
+     *
+     * <p>The chunk will be encoded in the format [nonce]+[encrypted data].
+     *
+     * <p>TODO(b/116575321): Choose a more descriptive method name after the code move is done.
+     */
+    void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException;
+
+    /**
+     * Returns the length in bytes that this chunk would be if encoded with {@link
+     * #writeChunkToWriter}.
+     */
+    int getEncodedLengthOfChunk(EncryptedChunk chunk);
+
+    /**
+     * Returns the {@link ChunkOrderingType} that must be included in the backup file, when using
+     * this decoder, so that the file may be correctly decoded.
+     */
+    @ChunkOrderingType
+    int getChunkOrderingType();
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
new file mode 100644
index 0000000..6b9be9f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import java.io.IOException;
+
+/**
+ * Encodes an {@link EncryptedChunk} as bytes, prepending the length of the chunk.
+ *
+ * <p>This allows us to decode the backup file during restore without any extra information about
+ * the boundaries of the chunks. The backup file should contain a chunk ordering in mode {@link
+ * ChunksMetadataProto#INLINE_LENGTHS}.
+ *
+ * <p>We use this implementation during key value backup.
+ */
+public class InlineLengthsEncryptedChunkEncoder implements EncryptedChunkEncoder {
+    public static final int BYTES_LENGTH = Integer.SIZE / Byte.SIZE;
+
+    private final LengthlessEncryptedChunkEncoder mLengthlessEncryptedChunkEncoder =
+            new LengthlessEncryptedChunkEncoder();
+
+    @Override
+    public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
+        int length = mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
+        writer.writeBytes(toByteArray(length));
+        mLengthlessEncryptedChunkEncoder.writeChunkToWriter(writer, chunk);
+    }
+
+    @Override
+    public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
+        return BYTES_LENGTH + mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
+    }
+
+    @Override
+    @ChunkOrderingType
+    public int getChunkOrderingType() {
+        return ChunksMetadataProto.INLINE_LENGTHS;
+    }
+
+    /**
+     * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to
+     * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code
+     * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}.
+     *
+     * <p>Equivalent to guava's Ints.toByteArray.
+     */
+    static byte[] toByteArray(int value) {
+        return new byte[] {
+            (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value
+        };
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
new file mode 100644
index 0000000..e707350
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import java.io.IOException;
+
+/**
+ * Encodes an {@link EncryptedChunk} as bytes without including any information about the length of
+ * the chunk.
+ *
+ * <p>In order for us to decode the backup file during restore it must include a chunk ordering in
+ * mode {@link ChunksMetadataProto#EXPLICIT_STARTS}, which contains the boundaries of the chunks in
+ * the encrypted file. This information allows us to decode the backup file and divide it into
+ * chunks without including the length of each chunk inline.
+ *
+ * <p>We use this implementation during full backup.
+ */
+public class LengthlessEncryptedChunkEncoder implements EncryptedChunkEncoder {
+    @Override
+    public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
+        writer.writeBytes(chunk.nonce());
+        writer.writeBytes(chunk.encryptedBytes());
+    }
+
+    @Override
+    public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
+        return chunk.nonce().length + chunk.encryptedBytes().length;
+    }
+
+    @Override
+    @ChunkOrderingType
+    public int getChunkOrderingType() {
+        return ChunksMetadataProto.EXPLICIT_STARTS;
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java
new file mode 100644
index 0000000..b211b0f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Writes data straight to an output stream. */
+public class RawBackupWriter implements BackupWriter {
+    private final OutputStream mOutputStream;
+    private long mBytesWritten;
+
+    /** Constructs a new writer which writes bytes to the given output stream. */
+    public RawBackupWriter(OutputStream outputStream) {
+        this.mOutputStream = outputStream;
+    }
+
+    @Override
+    public void writeBytes(byte[] bytes) throws IOException {
+        mOutputStream.write(bytes);
+        mBytesWritten += bytes.length;
+    }
+
+    @Override
+    public void writeChunk(long start, int length) throws IOException {
+        throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks");
+    }
+
+    @Override
+    public long getBytesWritten() {
+        return mBytesWritten;
+    }
+
+    @Override
+    public void flush() throws IOException {
+        mOutputStream.flush();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
new file mode 100644
index 0000000..a043c1f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Utility functions for wrapping and unwrapping tertiary keys. */
+public class KeyWrapUtils {
+    private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+    private static final int BITS_PER_BYTE = 8;
+    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
+    private static final String KEY_ALGORITHM = "AES";
+
+    /**
+     * Uses the secondary key to unwrap the wrapped tertiary key.
+     *
+     * @param secondaryKey The secondary key used to wrap the tertiary key.
+     * @param wrappedKey The wrapped tertiary key.
+     * @return The unwrapped tertiary key.
+     * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
+     */
+    public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
+            throws InvalidKeyException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException, NoSuchPaddingException {
+        if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
+            throw new InvalidKeyException(
+                    String.format(
+                            Locale.US,
+                            "Could not unwrap key wrapped with %s algorithm",
+                            wrappedKey.wrapAlgorithm));
+        }
+
+        if (wrappedKey.metadata == null) {
+            throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
+        }
+
+        if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
+            throw new InvalidKeyException(
+                    String.format(
+                            Locale.US,
+                            "Wrapped key was unexpected %s algorithm. Only support"
+                                + " AES/GCM/NoPadding.",
+                            wrappedKey.metadata.type));
+        }
+
+        Cipher cipher = getCipher();
+
+        cipher.init(
+                Cipher.UNWRAP_MODE,
+                secondaryKey,
+                new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));
+
+        return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
+    }
+
+    /**
+     * Wraps the tertiary key with the secondary key.
+     *
+     * @param secondaryKey The secondary key to use for wrapping.
+     * @param tertiaryKey The key to wrap.
+     * @return The wrapped key.
+     * @throws InvalidKeyException if the key is not good for wrapping.
+     * @throws IllegalBlockSizeException if there is an issue wrapping.
+     */
+    public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
+            throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        Cipher cipher = getCipher();
+        cipher.init(Cipher.WRAP_MODE, secondaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
+        wrappedKey.key = cipher.wrap(tertiaryKey);
+        wrappedKey.nonce = cipher.getIV();
+        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
+        wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
+        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
+        return wrappedKey;
+    }
+
+    /**
+     * Rewraps a tertiary key with a new secondary key.
+     *
+     * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
+     * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
+     * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
+     * @return The tertiary key, wrapped by {@code newSecondaryKey}.
+     * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
+     * @throws IllegalBlockSizeException if there is an issue wrapping.
+     */
+    public static WrappedKeyProto.WrappedKey rewrap(
+            SecretKey oldSecondaryKey,
+            SecretKey newSecondaryKey,
+            WrappedKeyProto.WrappedKey tertiaryKey)
+            throws InvalidKeyException, IllegalBlockSizeException,
+                    InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
+    }
+
+    private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+        return Cipher.getInstance(AES_GCM_MODE);
+    }
+
+    // Statics only
+    private KeyWrapUtils() {}
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java
new file mode 100644
index 0000000..6fb958b
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager.RecoverableKeyStoreSecondaryKeyManagerProvider;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/** Fetches the secondary key and uses it to unwrap the tertiary key during restore. */
+public class RestoreKeyFetcher {
+
+    /**
+     * Retrieves the secondary key with the given alias and uses it to unwrap the given wrapped
+     * tertiary key.
+     *
+     * @param secondaryKeyManagerProvider Provider which creates {@link
+     *     RecoverableKeyStoreSecondaryKeyManager}
+     * @param secondaryKeyAlias Alias of the secondary key used to wrap the tertiary key
+     * @param wrappedTertiaryKey Tertiary key wrapped with the secondary key above
+     * @return The unwrapped tertiary key
+     */
+    public static SecretKey unwrapTertiaryKey(
+            RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider,
+            String secondaryKeyAlias,
+            WrappedKeyProto.WrappedKey wrappedTertiaryKey)
+            throws KeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        Optional<RecoverableKeyStoreSecondaryKey> secondaryKey =
+                getSecondaryKey(secondaryKeyManagerProvider, secondaryKeyAlias);
+        if (!secondaryKey.isPresent()) {
+            throw new KeyException("No key:" + secondaryKeyAlias);
+        }
+
+        return KeyWrapUtils.unwrap(secondaryKey.get().getSecretKey(), wrappedTertiaryKey);
+    }
+
+    private static Optional<RecoverableKeyStoreSecondaryKey> getSecondaryKey(
+            RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider,
+            String secondaryKeyAlias)
+            throws KeyException {
+        try {
+            return secondaryKeyManagerProvider.get().get(secondaryKeyAlias);
+        } catch (InternalRecoveryServiceException | UnrecoverableKeyException e) {
+            throw new KeyException("Could not retrieve key:" + secondaryKeyAlias, e);
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
new file mode 100644
index 0000000..91b57cf
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import java.io.File;
+import java.time.Clock;
+import java.util.Optional;
+
+/**
+ * Helps schedule rotations of secondary keys.
+ *
+ * <p>TODO(b/72028016) Replace with a job.
+ */
+public class SecondaryKeyRotationScheduler {
+
+    private static final String TAG = "SecondaryKeyRotationScheduler";
+    private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+    private final Context mContext;
+    private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    private final CryptoSettings mCryptoSettings;
+    private final Clock mClock;
+
+    public SecondaryKeyRotationScheduler(
+            Context context,
+            RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager,
+            CryptoSettings cryptoSettings,
+            Clock clock) {
+        mContext = context;
+        mCryptoSettings = cryptoSettings;
+        mClock = clock;
+        mSecondaryKeyManager = secondaryKeyManager;
+    }
+
+    /**
+     * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This
+     * is only for testing purposes.
+     */
+    private boolean isForceRotationTestSentinelPresent() {
+        File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH);
+        if (file.exists()) {
+            file.delete();
+            return true;
+        }
+        return false;
+    }
+
+    /** Start the key rotation task if it's time to do so */
+    public void startRotationIfScheduled() {
+        if (isForceRotationTestSentinelPresent()) {
+            Slog.i(TAG, "Found force flag for secondary rotation. Starting now.");
+            startRotation();
+            return;
+        }
+
+        Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated();
+        if (!maybeLastRotated.isPresent()) {
+            Slog.v(TAG, "No previous rotation, scheduling from now.");
+            scheduleRotationFromNow();
+            return;
+        }
+
+        long lastRotated = maybeLastRotated.get();
+        long now = mClock.millis();
+
+        if (lastRotated > now) {
+            Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now.");
+            startRotation();
+            return;
+        }
+
+        long millisSinceLastRotation = now - lastRotated;
+        long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+        if (millisSinceLastRotation >= rotationInterval) {
+            Slog.i(
+                    TAG,
+                    "Last rotation was more than "
+                            + rotationInterval
+                            + "ms ("
+                            + millisSinceLastRotation
+                            + "ms) in the past. Rotate now.");
+            startRotation();
+        }
+
+        Slog.v(TAG, "No rotation required, last " + lastRotated + ".");
+    }
+
+    private void startRotation() {
+        scheduleRotationFromNow();
+        new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run();
+    }
+
+    private void scheduleRotationFromNow() {
+        mCryptoSettings.setSecondaryLastRotated(mClock.millis());
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java
new file mode 100644
index 0000000..a783579
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Optional;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Gets the correct tertiary key to use during a backup, rotating it if required.
+ *
+ * <p>Calling any method on this class will count a incremental backup against the app, and the key
+ * will be rotated if required.
+ */
+public class TertiaryKeyManager {
+
+    private static final String TAG = "TertiaryKeyMgr";
+
+    private final TertiaryKeyStore mKeyStore;
+    private final TertiaryKeyGenerator mKeyGenerator;
+    private final TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler;
+    private final RecoverableKeyStoreSecondaryKey mSecondaryKey;
+    private final String mPackageName;
+
+    private boolean mKeyRotated;
+    @Nullable private SecretKey mTertiaryKey;
+
+    public TertiaryKeyManager(
+            Context context,
+            SecureRandom secureRandom,
+            TertiaryKeyRotationScheduler tertiaryKeyRotationScheduler,
+            RecoverableKeyStoreSecondaryKey secondaryKey,
+            String packageName) {
+        mSecondaryKey = secondaryKey;
+        mPackageName = packageName;
+        mKeyGenerator = new TertiaryKeyGenerator(secureRandom);
+        mKeyStore = TertiaryKeyStore.newInstance(context, secondaryKey);
+        mTertiaryKeyRotationScheduler = tertiaryKeyRotationScheduler;
+    }
+
+    /**
+     * Returns either the previously used tertiary key, or a new tertiary key if there was no
+     * previous key or it needed to be rotated.
+     */
+    public SecretKey getKey()
+            throws InvalidKeyException, IOException, IllegalBlockSizeException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return mTertiaryKey;
+    }
+
+    /** Returns the key given by {@link #getKey()} wrapped by the secondary key. */
+    public WrappedKeyProto.WrappedKey getWrappedKey()
+            throws InvalidKeyException, IOException, IllegalBlockSizeException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return KeyWrapUtils.wrap(mSecondaryKey.getSecretKey(), mTertiaryKey);
+    }
+
+    /**
+     * Returns {@code true} if a new tertiary key was generated at the start of this session,
+     * otherwise {@code false}.
+     */
+    public boolean wasKeyRotated()
+            throws InvalidKeyException, IllegalBlockSizeException, IOException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return mKeyRotated;
+    }
+
+    private void init()
+            throws IllegalBlockSizeException, InvalidKeyException, IOException,
+                    NoSuchAlgorithmException, NoSuchPaddingException,
+                    InvalidAlgorithmParameterException {
+        if (mTertiaryKey != null) {
+            return;
+        }
+
+        Optional<SecretKey> key = getExistingKeyIfNotRotated();
+
+        if (!key.isPresent()) {
+            Slog.d(TAG, "Generating new tertiary key for " + mPackageName);
+
+            key = Optional.of(mKeyGenerator.generate());
+            mKeyRotated = true;
+            mTertiaryKeyRotationScheduler.recordKeyRotation(mPackageName);
+            mKeyStore.save(mPackageName, key.get());
+        }
+
+        mTertiaryKey = key.get();
+
+        mTertiaryKeyRotationScheduler.recordBackup(mPackageName);
+    }
+
+    private Optional<SecretKey> getExistingKeyIfNotRotated()
+            throws InvalidKeyException, IOException, InvalidAlgorithmParameterException,
+                    NoSuchAlgorithmException, NoSuchPaddingException {
+        if (mTertiaryKeyRotationScheduler.isKeyRotationDue(mPackageName)) {
+            Slog.i(TAG, "Tertiary key rotation was required for " + mPackageName);
+            return Optional.empty();
+        } else {
+            Slog.i(TAG, "Tertiary key rotation was not required");
+            return mKeyStore.load(mPackageName);
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java
new file mode 100644
index 0000000..f16a68d
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Schedules tertiary key rotations in a staggered fashion.
+ *
+ * <p>Apps are due a key rotation after a certain number of backups. Rotations are then staggerered
+ * over a period of time, through restricting the number of rotations allowed in a 24-hour window.
+ * This will causes the apps to enter a staggered cycle of regular rotations.
+ *
+ * <p>Note: the methods in this class are not optimized to be super fast. They make blocking IO to
+ * ensure that scheduler information is committed to disk, so that it is available after the user
+ * turns their device off and on. This ought to be fine as
+ *
+ * <ul>
+ *   <li>It will be invoked before a backup, so should never be invoked on the UI thread
+ *   <li>It will be invoked before a backup, so the vast amount of time is spent on the backup, not
+ *       writing tiny amounts of data to disk.
+ * </ul>
+ */
+public class TertiaryKeyRotationScheduler {
+    /** Default number of key rotations allowed within 24 hours. */
+    private static final int KEY_ROTATION_LIMIT = 2;
+
+    /** A new instance, using {@code context} to determine where to store state. */
+    public static TertiaryKeyRotationScheduler getInstance(Context context) {
+        TertiaryKeyRotationWindowedCount windowedCount =
+                TertiaryKeyRotationWindowedCount.getInstance(context);
+        TertiaryKeyRotationTracker tracker = TertiaryKeyRotationTracker.getInstance(context);
+        return new TertiaryKeyRotationScheduler(tracker, windowedCount, KEY_ROTATION_LIMIT);
+    }
+
+    private final TertiaryKeyRotationTracker mTracker;
+    private final TertiaryKeyRotationWindowedCount mWindowedCount;
+    private final int mMaximumRotationsPerWindow;
+
+    /**
+     * A new instance.
+     *
+     * @param tracker Tracks how many times each application has backed up.
+     * @param windowedCount Tracks how many rotations have happened in the last 24 hours.
+     * @param maximumRotationsPerWindow The maximum number of key rotations allowed per 24 hours.
+     */
+    @VisibleForTesting
+    TertiaryKeyRotationScheduler(
+            TertiaryKeyRotationTracker tracker,
+            TertiaryKeyRotationWindowedCount windowedCount,
+            int maximumRotationsPerWindow) {
+        mTracker = tracker;
+        mWindowedCount = windowedCount;
+        mMaximumRotationsPerWindow = maximumRotationsPerWindow;
+    }
+
+    /**
+     * Returns {@code true} if the app with {@code packageName} is due having its key rotated.
+     *
+     * <p>This ought to be queried before backing up an app, to determine whether to do an
+     * incremental backup or a full backup. (A full backup forces key rotation.)
+     */
+    public boolean isKeyRotationDue(String packageName) {
+        if (mWindowedCount.getCount() >= mMaximumRotationsPerWindow) {
+            return false;
+        }
+        return mTracker.isKeyRotationDue(packageName);
+    }
+
+    /**
+     * Records that a backup happened for the app with the given {@code packageName}.
+     *
+     * <p>Each backup brings the app closer to the point at which a key rotation is due.
+     */
+    public void recordBackup(String packageName) {
+        mTracker.recordBackup(packageName);
+    }
+
+    /**
+     * Records a key rotation happened for the app with the given {@code packageName}.
+     *
+     * <p>This resets the countdown until the next key rotation is due.
+     */
+    public void recordKeyRotation(String packageName) {
+        mTracker.resetCountdown(packageName);
+        mWindowedCount.record();
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
new file mode 100644
index 0000000..1a281e7
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+
+/**
+ * Tracks when a tertiary key rotation is due.
+ *
+ * <p>After a certain number of incremental backups, the device schedules a full backup, which will
+ * generate a new encryption key, effecting a key rotation. We should do this on a regular basis so
+ * that if a key does become compromised it has limited value to the attacker.
+ *
+ * <p>No additional synchronization of this class is provided. Only one instance should be used at
+ * any time. This should be fine as there should be no parallelism in backups.
+ */
+public class TertiaryKeyRotationTracker {
+    private static final int MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION = 31;
+    private static final String SHARED_PREFERENCES_NAME = "tertiary_key_rotation_tracker";
+
+    private static final String TAG = "TertiaryKeyRotationTracker";
+    private static final boolean DEBUG = false;
+
+    /**
+     * A new instance, using {@code context} to commit data to disk via {@link SharedPreferences}.
+     */
+    public static TertiaryKeyRotationTracker getInstance(Context context) {
+        return new TertiaryKeyRotationTracker(
+                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE),
+                MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION);
+    }
+
+    private final SharedPreferences mSharedPreferences;
+    private final int mMaxBackupsTillRotation;
+
+    /**
+     * New instance, storing data in {@code sharedPreferences} and initializing backup countdown to
+     * {@code maxBackupsTillRotation}.
+     */
+    @VisibleForTesting
+    TertiaryKeyRotationTracker(SharedPreferences sharedPreferences, int maxBackupsTillRotation) {
+        checkArgument(
+                maxBackupsTillRotation >= 0,
+                String.format(
+                        Locale.US,
+                        "maxBackupsTillRotation should be non-negative but was %d",
+                        maxBackupsTillRotation));
+        mSharedPreferences = sharedPreferences;
+        mMaxBackupsTillRotation = maxBackupsTillRotation;
+    }
+
+    /**
+     * Returns {@code true} if the given app is due having its key rotated.
+     *
+     * @param packageName The package name of the app.
+     */
+    public boolean isKeyRotationDue(String packageName) {
+        return getBackupsSinceRotation(packageName) >= mMaxBackupsTillRotation;
+    }
+
+    /**
+     * Records that an incremental backup has occurred. Each incremental backup brings the app
+     * closer to the time when its key should be rotated.
+     *
+     * @param packageName The package name of the app for which the backup occurred.
+     */
+    public void recordBackup(String packageName) {
+        int backupsSinceRotation = getBackupsSinceRotation(packageName) + 1;
+        mSharedPreferences.edit().putInt(packageName, backupsSinceRotation).apply();
+        if (DEBUG) {
+            Slog.d(
+                    TAG,
+                    String.format(
+                            Locale.US,
+                            "Incremental backup for %s. %d backups until key rotation.",
+                            packageName,
+                            Math.max(
+                                    0,
+                                    mMaxBackupsTillRotation
+                                            - backupsSinceRotation)));
+        }
+    }
+
+    /**
+     * Resets the rotation delay for the given app. Should be invoked after a key rotation.
+     *
+     * @param packageName Package name of the app whose key has rotated.
+     */
+    public void resetCountdown(String packageName) {
+        mSharedPreferences.edit().putInt(packageName, 0).apply();
+    }
+
+    /** Marks all enrolled packages for key rotation. */
+    public void markAllForRotation() {
+        SharedPreferences.Editor editor = mSharedPreferences.edit();
+        for (String packageName : mSharedPreferences.getAll().keySet()) {
+            editor.putInt(packageName, mMaxBackupsTillRotation);
+        }
+        editor.apply();
+    }
+
+    private int getBackupsSinceRotation(String packageName) {
+        return mSharedPreferences.getInt(packageName, 0);
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java
new file mode 100644
index 0000000..b90343a
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tracks (and commits to disk) how many key rotations have happened in the last 24 hours. This
+ * allows us to limit (and therefore stagger) the number of key rotations in a given period of time.
+ *
+ * <p>Note to engineers thinking of replacing the below with fancier algorithms and data structures:
+ * we expect the total size of this count at any time to be below however many rotations we allow in
+ * the window, which is going to be in single digits. Any changes that mean we write to disk more
+ * frequently, that the code is no longer resistant to clock changes, or that the code is more
+ * difficult to understand are almost certainly not worthwhile.
+ */
+public class TertiaryKeyRotationWindowedCount {
+    private static final String TAG = "TertiaryKeyRotCount";
+
+    private static final int WINDOW_IN_HOURS = 24;
+    private static final String LOG_FILE_NAME = "tertiary_key_rotation_windowed_count";
+
+    private final Clock mClock;
+    private final File mFile;
+    private ArrayList<Long> mEvents;
+
+    /** Returns a new instance, persisting state to the files dir of {@code context}. */
+    public static TertiaryKeyRotationWindowedCount getInstance(Context context) {
+        File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
+        return new TertiaryKeyRotationWindowedCount(logFile, Clock.systemDefaultZone());
+    }
+
+    /** A new instance, committing state to {@code file}, and reading time from {@code clock}. */
+    @VisibleForTesting
+    TertiaryKeyRotationWindowedCount(File file, Clock clock) {
+        mFile = file;
+        mClock = clock;
+        mEvents = new ArrayList<>();
+        try {
+            loadFromFile();
+        } catch (IOException e) {
+            Slog.e(TAG, "Error reading " + LOG_FILE_NAME, e);
+        }
+    }
+
+    /** Records a key rotation at the current time. */
+    public void record() {
+        mEvents.add(mClock.millis());
+        compact();
+        try {
+            saveToFile();
+        } catch (IOException e) {
+            Slog.e(TAG, "Error saving " + LOG_FILE_NAME, e);
+        }
+    }
+
+    /** Returns the number of key rotation that have been recorded in the window. */
+    public int getCount() {
+        compact();
+        return mEvents.size();
+    }
+
+    private void compact() {
+        long minimumTimestamp = getMinimumTimestamp();
+        long now = mClock.millis();
+        ArrayList<Long> compacted = new ArrayList<>();
+        for (long event : mEvents) {
+            if (event >= minimumTimestamp && event <= now) {
+                compacted.add(event);
+            }
+        }
+        mEvents = compacted;
+    }
+
+    private long getMinimumTimestamp() {
+        return mClock.millis() - TimeUnit.HOURS.toMillis(WINDOW_IN_HOURS) + 1;
+    }
+
+    private void loadFromFile() throws IOException {
+        if (!mFile.exists()) {
+            return;
+        }
+        try (FileInputStream fis = new FileInputStream(mFile);
+                DataInputStream dis = new DataInputStream(fis)) {
+            while (true) {
+                mEvents.add(dis.readLong());
+            }
+        } catch (EOFException eof) {
+            // expected
+        }
+    }
+
+    private void saveToFile() throws IOException {
+        // File size is maximum number of key rotations in window multiplied by 8 bytes, which is
+        // why
+        // we just overwrite it each time. We expect it will always be less than 100 bytes in size.
+        try (FileOutputStream fos = new FileOutputStream(mFile);
+                DataOutputStream dos = new DataOutputStream(fos)) {
+            for (long event : mEvents) {
+                dos.writeLong(event);
+            }
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java
new file mode 100644
index 0000000..01444bf
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.storage.BackupEncryptionDb;
+import com.android.server.backup.encryption.storage.TertiaryKey;
+import com.android.server.backup.encryption.storage.TertiaryKeysTable;
+
+import com.google.protobuf.nano.CodedOutputByteBufferNano;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Stores backup package keys. Each application package has its own {@link SecretKey}, which is used
+ * to encrypt the backup data. These keys are then wrapped by a master backup key, and stored in
+ * their wrapped form on disk and on the backup server.
+ *
+ * <p>For now this code only implements writing to disk. Once the backup server is ready, it will be
+ * extended to sync the keys there, also.
+ */
+public class TertiaryKeyStore {
+
+    private final RecoverableKeyStoreSecondaryKey mSecondaryKey;
+    private final BackupEncryptionDb mDatabase;
+
+    /**
+     * Creates an instance, using {@code secondaryKey} to wrap tertiary keys, and storing them in
+     * the database.
+     */
+    public static TertiaryKeyStore newInstance(
+            Context context, RecoverableKeyStoreSecondaryKey secondaryKey) {
+        return new TertiaryKeyStore(secondaryKey, BackupEncryptionDb.newInstance(context));
+    }
+
+    private TertiaryKeyStore(
+            RecoverableKeyStoreSecondaryKey secondaryKey, BackupEncryptionDb database) {
+        mSecondaryKey = secondaryKey;
+        mDatabase = database;
+    }
+
+    /**
+     * Saves the given key.
+     *
+     * @param applicationName The package name of the application for which this key will be used to
+     *     encrypt data. e.g., "com.example.app".
+     * @param key The key.
+     * @throws InvalidKeyException if the backup key is not capable of wrapping.
+     * @throws IOException if there is an issue writing to the database.
+     */
+    public void save(String applicationName, SecretKey key)
+            throws IOException, InvalidKeyException, IllegalBlockSizeException,
+                    NoSuchPaddingException, NoSuchAlgorithmException {
+        checkApplicationName(applicationName);
+
+        byte[] keyBytes = getEncodedKey(KeyWrapUtils.wrap(mSecondaryKey.getSecretKey(), key));
+
+        long pk;
+        try {
+            pk =
+                    mDatabase
+                            .getTertiaryKeysTable()
+                            .addKey(
+                                    new TertiaryKey(
+                                            mSecondaryKey.getAlias(), applicationName, keyBytes));
+        } finally {
+            mDatabase.close();
+        }
+
+        if (pk == -1) {
+            throw new IOException("Failed to commit to db");
+        }
+    }
+
+    /**
+     * Tries to load a key for the given application.
+     *
+     * @param applicationName The package name of the application, e.g. "com.example.app".
+     * @return The key if it is exists, {@link Optional#empty()} ()} otherwise.
+     * @throws InvalidKeyException if the backup key is not good for unwrapping.
+     * @throws IOException if there is a problem loading the key from the database.
+     */
+    public Optional<SecretKey> load(String applicationName)
+            throws IOException, InvalidKeyException, InvalidAlgorithmParameterException,
+                    NoSuchAlgorithmException, NoSuchPaddingException {
+        checkApplicationName(applicationName);
+
+        Optional<TertiaryKey> keyFromDb;
+        try {
+            keyFromDb =
+                    mDatabase
+                            .getTertiaryKeysTable()
+                            .getKey(mSecondaryKey.getAlias(), applicationName);
+        } finally {
+            mDatabase.close();
+        }
+
+        if (!keyFromDb.isPresent()) {
+            return Optional.empty();
+        }
+
+        WrappedKeyProto.WrappedKey wrappedKey =
+                WrappedKeyProto.WrappedKey.parseFrom(keyFromDb.get().getWrappedKeyBytes());
+        return Optional.of(KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey));
+    }
+
+    /**
+     * Loads keys for all applications.
+     *
+     * @return All of the keys in a map keyed by package name.
+     * @throws IOException if there is an issue loading from the database.
+     * @throws InvalidKeyException if the backup key is not an appropriate key for unwrapping.
+     */
+    public Map<String, SecretKey> getAll()
+            throws IOException, InvalidKeyException, InvalidAlgorithmParameterException,
+                    NoSuchAlgorithmException, NoSuchPaddingException {
+        Map<String, TertiaryKey> tertiaries;
+        try {
+            tertiaries = mDatabase.getTertiaryKeysTable().getAllKeys(mSecondaryKey.getAlias());
+        } finally {
+            mDatabase.close();
+        }
+
+        Map<String, SecretKey> unwrappedKeys = new ArrayMap<>();
+        for (String applicationName : tertiaries.keySet()) {
+            WrappedKeyProto.WrappedKey wrappedKey =
+                    WrappedKeyProto.WrappedKey.parseFrom(
+                            tertiaries.get(applicationName).getWrappedKeyBytes());
+            unwrappedKeys.put(
+                    applicationName, KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey));
+        }
+
+        return unwrappedKeys;
+    }
+
+    /**
+     * Adds all wrapped keys to the database.
+     *
+     * @throws IOException if an error occurred adding a wrapped key.
+     */
+    public void putAll(Map<String, WrappedKeyProto.WrappedKey> wrappedKeysByApplicationName)
+            throws IOException {
+        TertiaryKeysTable tertiaryKeysTable = mDatabase.getTertiaryKeysTable();
+        try {
+
+            for (String applicationName : wrappedKeysByApplicationName.keySet()) {
+                byte[] keyBytes = getEncodedKey(wrappedKeysByApplicationName.get(applicationName));
+                long primaryKey =
+                        tertiaryKeysTable.addKey(
+                                new TertiaryKey(
+                                        mSecondaryKey.getAlias(), applicationName, keyBytes));
+
+                if (primaryKey == -1) {
+                    throw new IOException("Failed to commit to db");
+                }
+            }
+
+        } finally {
+            mDatabase.close();
+        }
+    }
+
+    private static void checkApplicationName(String applicationName) {
+        checkArgument(!applicationName.isEmpty(), "applicationName must not be empty string.");
+        checkArgument(!applicationName.contains("/"), "applicationName must not contain slash.");
+    }
+
+    private byte[] getEncodedKey(WrappedKeyProto.WrappedKey key) throws IOException {
+        byte[] buffer = new byte[key.getSerializedSize()];
+        CodedOutputByteBufferNano out = CodedOutputByteBufferNano.newInstance(buffer);
+        key.writeTo(out);
+        return buffer;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java
new file mode 100644
index 0000000..56e1c05
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.android.internal.util.Preconditions.checkState;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+import com.android.server.backup.encryption.tasks.DecryptedChunkOutput;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Builds a key value backup set from plaintext chunks. Computes a digest over the sorted SHA-256
+ * hashes of the chunks.
+ */
+public class DecryptedChunkKvOutput implements DecryptedChunkOutput {
+    @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256";
+
+    private final ChunkHasher mChunkHasher;
+    private final List<KeyValuePairProto.KeyValuePair> mUnsortedPairs = new ArrayList<>();
+    private final List<ChunkHash> mUnsortedHashes = new ArrayList<>();
+    private boolean mClosed;
+
+    /** Constructs a new instance which computers the digest using the given hasher. */
+    public DecryptedChunkKvOutput(ChunkHasher chunkHasher) {
+        mChunkHasher = chunkHasher;
+    }
+
+    @Override
+    public DecryptedChunkOutput open() {
+        // As we don't have any resources there is nothing to open.
+        return this;
+    }
+
+    @Override
+    public void processChunk(byte[] plaintextBuffer, int length)
+            throws IOException, InvalidKeyException {
+        checkState(!mClosed, "Cannot process chunk after close()");
+        KeyValuePairProto.KeyValuePair kvPair = new KeyValuePairProto.KeyValuePair();
+        KeyValuePairProto.KeyValuePair.mergeFrom(kvPair, plaintextBuffer, 0, length);
+        mUnsortedPairs.add(kvPair);
+        // TODO(b/71492289): Update ChunkHasher to accept offset and length so we don't have to copy
+        // the buffer into a smaller array.
+        mUnsortedHashes.add(mChunkHasher.computeHash(Arrays.copyOf(plaintextBuffer, length)));
+    }
+
+    @Override
+    public void close() {
+        // As we don't have any resources there is nothing to close.
+        mClosed = true;
+    }
+
+    @Override
+    public byte[] getDigest() throws NoSuchAlgorithmException {
+        checkState(mClosed, "Must close() before getDigest()");
+        MessageDigest digest = getMessageDigest();
+        Collections.sort(mUnsortedHashes);
+        for (ChunkHash hash : mUnsortedHashes) {
+            digest.update(hash.getHash());
+        }
+        return digest.digest();
+    }
+
+    private static MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance(DIGEST_ALGORITHM);
+    }
+
+    /**
+     * Returns the key value pairs from the backup, sorted lexicographically by key.
+     *
+     * <p>You must call {@link #close} first.
+     */
+    public List<KeyValuePairProto.KeyValuePair> getPairs() {
+        checkState(mClosed, "Must close() before getPairs()");
+        Collections.sort(
+                mUnsortedPairs,
+                new Comparator<KeyValuePairProto.KeyValuePair>() {
+                    @Override
+                    public int compare(
+                            KeyValuePairProto.KeyValuePair o1, KeyValuePairProto.KeyValuePair o2) {
+                        return o1.key.compareTo(o2.key);
+                    }
+                });
+        return mUnsortedPairs;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
new file mode 100644
index 0000000..b3518e1
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Builds a {@link KeyValueListingProto.KeyValueListing}, which is a nano proto and so has no
+ * builder.
+ */
+public class KeyValueListingBuilder {
+    private final List<KeyValueListingProto.KeyValueEntry> mEntries = new ArrayList<>();
+
+    /** Adds a new pair entry to the listing. */
+    public KeyValueListingBuilder addPair(String key, ChunkHash hash) {
+        checkArgument(key.length() != 0, "Key must have non-zero length");
+        checkNotNull(hash, "Hash must not be null");
+
+        KeyValueListingProto.KeyValueEntry entry = new KeyValueListingProto.KeyValueEntry();
+        entry.key = key;
+        entry.hash = hash.getHash();
+        mEntries.add(entry);
+
+        return this;
+    }
+
+    /** Adds all pairs contained in a map, where the map is from key to hash. */
+    public KeyValueListingBuilder addAll(Map<String, ChunkHash> map) {
+        for (Entry<String, ChunkHash> entry : map.entrySet()) {
+            addPair(entry.getKey(), entry.getValue());
+        }
+
+        return this;
+    }
+
+    /** Returns a new listing containing all the pairs added so far. */
+    public KeyValueListingProto.KeyValueListing build() {
+        if (mEntries.size() == 0) {
+            return emptyListing();
+        }
+
+        KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing();
+        listing.entries = new KeyValueListingProto.KeyValueEntry[mEntries.size()];
+        mEntries.toArray(listing.entries);
+        return listing;
+    }
+
+    /** Returns a new listing which does not contain any pairs. */
+    public static KeyValueListingProto.KeyValueListing emptyListing() {
+        KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing();
+        listing.entries = KeyValueListingProto.KeyValueEntry.emptyArray();
+        return listing;
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
new file mode 100644
index 0000000..f67f100
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Accepts the plaintext bytes of decrypted chunks and writes them to some output. Also keeps track
+ * of the message digest of the chunks.
+ */
+public interface DecryptedChunkOutput extends Closeable {
+    /**
+     * Opens whatever output the implementation chooses, ready to process chunks.
+     *
+     * @return {@code this}, to allow use with try-with-resources
+     */
+    DecryptedChunkOutput open() throws IOException, NoSuchAlgorithmException;
+
+    /**
+     * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also
+     * updates the digest with the chunk.
+     *
+     * <p>You must call {@link #open()} before this method, and you may not call it after calling
+     * {@link Closeable#close()}.
+     *
+     * @param plaintextBuffer An array containing the bytes of the plaintext of the chunk, starting
+     *     at index 0.
+     * @param length The length in bytes of the plaintext contained in {@code plaintextBuffer}.
+     */
+    void processChunk(byte[] plaintextBuffer, int length)
+            throws IOException, InvalidKeyException, NoSuchAlgorithmException;
+
+    /**
+     * Returns the message digest of all the chunks processed by {@link #processChunk}.
+     *
+     * <p>You must call {@link Closeable#close()} before calling this method.
+     */
+    byte[] getDigest() throws NoSuchAlgorithmException;
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java
new file mode 100644
index 0000000..d20cd4c
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.app.backup.BackupDataInput;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkEncryptor;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.kv.KeyValueListingBuilder;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+
+/**
+ * Reads key value backup data from an input, converts each pair into a chunk and encrypts the
+ * chunks.
+ *
+ * <p>The caller should pass in the key value listing from the previous backup, if there is one.
+ * This class emits chunks for both existing and new pairs, using the provided listing to
+ * determine the hashes of pairs that already exist. During the backup it computes the new listing,
+ * which the caller should store on disk and pass in at the start of the next backup.
+ *
+ * <p>Also computes the message digest, which is {@code SHA-256(chunk hashes sorted
+ * lexicographically)}.
+ */
+public class KvBackupEncrypter implements BackupEncrypter {
+    private final BackupDataInput mBackupDataInput;
+
+    private KeyValueListingProto.KeyValueListing mOldKeyValueListing;
+    @Nullable private KeyValueListingBuilder mNewKeyValueListing;
+
+    /**
+     * Constructs a new instance which reads data from the given input.
+     *
+     * <p>By default this performs non-incremental backup, call {@link #setOldKeyValueListing} to
+     * perform incremental backup.
+     */
+    public KvBackupEncrypter(BackupDataInput backupDataInput) {
+        mBackupDataInput = backupDataInput;
+        mOldKeyValueListing = KeyValueListingBuilder.emptyListing();
+    }
+
+    /** Sets the old listing to perform incremental backup against. */
+    public void setOldKeyValueListing(KeyValueListingProto.KeyValueListing oldKeyValueListing) {
+        mOldKeyValueListing = oldKeyValueListing;
+    }
+
+    @Override
+    public Result backup(
+            SecretKey secretKey,
+            @Nullable byte[] unusedFingerprintMixerSalt,
+            Set<ChunkHash> unusedExistingChunks)
+            throws IOException, GeneralSecurityException {
+        ChunkHasher chunkHasher = new ChunkHasher(secretKey);
+        ChunkEncryptor chunkEncryptor = new ChunkEncryptor(secretKey, new SecureRandom());
+        mNewKeyValueListing = new KeyValueListingBuilder();
+        List<ChunkHash> allChunks = new ArrayList<>();
+        List<EncryptedChunk> newChunks = new ArrayList<>();
+
+        Map<String, ChunkHash> existingChunksToReuse = buildPairMap(mOldKeyValueListing);
+
+        while (mBackupDataInput.readNextHeader()) {
+            String key = mBackupDataInput.getKey();
+            Optional<byte[]> value = readEntireValue(mBackupDataInput);
+
+            // As this pair exists in the new backup, we don't need to add it from the previous
+            // backup.
+            existingChunksToReuse.remove(key);
+
+            // If the value is not present then this key has been deleted.
+            if (value.isPresent()) {
+                EncryptedChunk newChunk =
+                        createEncryptedChunk(chunkHasher, chunkEncryptor, key, value.get());
+                allChunks.add(newChunk.key());
+                newChunks.add(newChunk);
+                mNewKeyValueListing.addPair(key, newChunk.key());
+            }
+        }
+
+        allChunks.addAll(existingChunksToReuse.values());
+
+        mNewKeyValueListing.addAll(existingChunksToReuse);
+
+        return new Result(allChunks, newChunks, createMessageDigest(allChunks));
+    }
+
+    /**
+     * Returns a listing containing the pairs in the new backup.
+     *
+     * <p>You must call {@link #backup} first.
+     */
+    public KeyValueListingProto.KeyValueListing getNewKeyValueListing() {
+        checkState(mNewKeyValueListing != null, "Must call backup() first");
+        return mNewKeyValueListing.build();
+    }
+
+    private static Map<String, ChunkHash> buildPairMap(
+            KeyValueListingProto.KeyValueListing listing) {
+        Map<String, ChunkHash> map = new HashMap<>();
+        for (KeyValueListingProto.KeyValueEntry entry : listing.entries) {
+            map.put(entry.key, new ChunkHash(entry.hash));
+        }
+        return map;
+    }
+
+    private EncryptedChunk createEncryptedChunk(
+            ChunkHasher chunkHasher, ChunkEncryptor chunkEncryptor, String key, byte[] value)
+            throws InvalidKeyException, IllegalBlockSizeException {
+        KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair();
+        pair.key = key;
+        pair.value = Arrays.copyOf(value, value.length);
+
+        byte[] plaintext = KeyValuePairProto.KeyValuePair.toByteArray(pair);
+        return chunkEncryptor.encrypt(chunkHasher.computeHash(plaintext), plaintext);
+    }
+
+    private static byte[] createMessageDigest(List<ChunkHash> allChunks)
+            throws NoSuchAlgorithmException {
+        MessageDigest messageDigest =
+                MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM);
+        // TODO:b/141531271 Extract sorted chunks code to utility class
+        List<ChunkHash> sortedChunks = new ArrayList<>(allChunks);
+        Collections.sort(sortedChunks);
+        for (ChunkHash hash : sortedChunks) {
+            messageDigest.update(hash.getHash());
+        }
+        return messageDigest.digest();
+    }
+
+    private static Optional<byte[]> readEntireValue(BackupDataInput input) throws IOException {
+        // A negative data size indicates that this key should be deleted.
+        if (input.getDataSize() < 0) {
+            return Optional.empty();
+        }
+
+        byte[] value = new byte[input.getDataSize()];
+        int bytesRead = 0;
+        while (bytesRead < value.length) {
+            bytesRead += input.readEntityData(value, bytesRead, value.length - bytesRead);
+        }
+        return Optional.of(value);
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
new file mode 100644
index 0000000..77cfded
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.LockScreenRequiredException;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+/**
+ * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new
+ * key is synced.
+ */
+public class StartSecondaryKeyRotationTask {
+    private static final String TAG = "BE-StSecondaryKeyRotTsk";
+
+    private final CryptoSettings mCryptoSettings;
+    private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+
+    public StartSecondaryKeyRotationTask(
+            CryptoSettings cryptoSettings,
+            RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) {
+        mCryptoSettings = Preconditions.checkNotNull(cryptoSettings);
+        mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager);
+    }
+
+    /** Begin the key rotation */
+    public void run() {
+        Slog.i(TAG, "Attempting to initiate a secondary key rotation.");
+
+        Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias();
+        if (!maybeCurrentAlias.isPresent()) {
+            Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation.");
+            return;
+        }
+        String currentAlias = maybeCurrentAlias.get();
+
+        Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias();
+        if (maybeNextAlias.isPresent()) {
+            String nextAlias = maybeNextAlias.get();
+            if (nextAlias.equals(currentAlias)) {
+                // Shouldn't be possible, but guard against accidentally deleting the active key.
+                Slog.e(TAG, "Was already trying to rotate to what is already the active key.");
+            } else {
+                Slog.w(TAG, "Was already rotating to another key. Cancelling that.");
+                try {
+                    mSecondaryKeyManager.remove(nextAlias);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Could not remove old key", e);
+                }
+            }
+            mCryptoSettings.removeNextSecondaryKeyAlias();
+        }
+
+        RecoverableKeyStoreSecondaryKey newSecondaryKey;
+        try {
+            newSecondaryKey = mSecondaryKeyManager.generate();
+        } catch (LockScreenRequiredException e) {
+            Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e);
+            return;
+        } catch (InternalRecoveryServiceException e) {
+            Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e);
+            return;
+        } catch (UnrecoverableKeyException e) {
+            Slog.e(TAG, "Failed to get key after generating, failed to rotate", e);
+            return;
+        }
+
+        String alias = newSecondaryKey.getAlias();
+        Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'.");
+        try {
+            mCryptoSettings.setNextSecondaryAlias(alias);
+            Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to");
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unexpected error setting next alias", e);
+            try {
+                mSecondaryKeyManager.remove(alias);
+            } catch (Exception err) {
+                Slog.wtf(TAG, "Failed to remove generated key after encountering error", err);
+            }
+        }
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp
new file mode 100644
index 0000000..2a36dcf
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_robolectric_test {
+    name: "BackupEncryptionRoboTests",
+    srcs: [
+        "src/**/*.java",
+//        ":FrameworksServicesRoboShadows",
+    ],
+    java_resource_dirs: ["config"],
+    libs: [
+        "backup-encryption-protos",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.rules",
+    ],
+    instrumentation_for: "BackupEncryption",
+}
+
+filegroup {
+    name: "BackupEncryptionRoboShadows",
+    srcs: ["src/com/android/server/testing/shadows/**/*.java"],
+}
diff --git a/packages/BackupEncryption/test/robolectric/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric/AndroidManifest.xml
new file mode 100644
index 0000000..ae5cdd9
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          coreApp="true"
+          package="com.android.server.backup.encryption.robotests">
+
+    <application/>
+
+</manifest>
diff --git a/packages/BackupEncryption/test/robolectric/config/robolectric.properties b/packages/BackupEncryption/test/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..26fceb3
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/config/robolectric.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+sdk=NEWEST_SDK
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java
new file mode 100644
index 0000000..979b3d5
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.Application;
+import android.security.keystore.recovery.RecoveryController;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Optional;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowRecoveryController.class)
+public class CryptoSettingsTest {
+
+    private static final String TEST_KEY_ALIAS =
+            "com.android.server.backup.encryption/keystore/08120c326b928ff34c73b9c58581da63";
+
+    private CryptoSettings mCryptoSettings;
+    private Application mApplication;
+
+    @Before
+    public void setUp() {
+        ShadowRecoveryController.reset();
+
+        mApplication = ApplicationProvider.getApplicationContext();
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication);
+    }
+
+    @Test
+    public void getActiveSecondaryAlias_isInitiallyAbsent() {
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getActiveSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void getNextSecondaryAlias_isInitiallyAbsent() {
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getNextSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void isInitialized_isInitiallyFalse() {
+        assertThat(mCryptoSettings.getIsInitialized()).isFalse();
+    }
+
+    @Test
+    public void setActiveSecondaryAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void setNextSecondaryAlias_inRecoveryController_setsAlias() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void setNextSecondaryAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void removeNextSecondaryAlias_removesIt() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+
+        mCryptoSettings.removeNextSecondaryKeyAlias();
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void initializeWithKeyAlias_setsAsInitialized() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getIsInitialized()).isTrue();
+    }
+
+    @Test
+    public void initializeWithKeyAlias_setsActiveAlias() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void initializeWithKeyAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void initializeWithKeyAlias_throwsIfAlreadyInitialized() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void getSecondaryLastRotated_returnsEmptyInitially() {
+        assertThat(mCryptoSettings.getSecondaryLastRotated()).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void getSecondaryLastRotated_returnsTimestampAfterItIsSet() {
+        long timestamp = 1000001;
+
+        mCryptoSettings.setSecondaryLastRotated(timestamp);
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(timestamp);
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_notSet_returnsOptionalAbsent() {
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_isSet_returnsSetValue() {
+        String secondaryKeyVersion = "some_secondary_key";
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion);
+
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get())
+                .isEqualTo(secondaryKeyVersion);
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_isSetMultipleTimes_returnsLastSetValue() {
+        String secondaryKeyVersion1 = "some_secondary_key";
+        String secondaryKeyVersion2 = "another_secondary_key";
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion1);
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion2);
+
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get())
+                .isEqualTo(secondaryKeyVersion2);
+    }
+
+    @Test
+    public void clearAllSettingsForBackup_clearsStateForBackup() throws Exception {
+        String key1 = "key1";
+        String key2 = "key2";
+        String ancestralKey = "ancestral_key";
+        setAliasIsInRecoveryController(key1);
+        setAliasIsInRecoveryController(key2);
+        mCryptoSettings.setActiveSecondaryKeyAlias(key1);
+        mCryptoSettings.setNextSecondaryAlias(key2);
+        mCryptoSettings.setSecondaryLastRotated(100001);
+        mCryptoSettings.setAncestralSecondaryKeyVersion(ancestralKey);
+
+        mCryptoSettings.clearAllSettingsForBackup();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()).isEqualTo(ancestralKey);
+    }
+
+    private void setAliasIsInRecoveryController(String alias) throws Exception {
+        RecoveryController recoveryController = RecoveryController.getInstance(mApplication);
+        recoveryController.generateKey(alias);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
new file mode 100644
index 0000000..c12464c
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkHashTest {
+    private static final int HASH_LENGTH_BYTES = 256 / 8;
+    private static final byte[] TEST_HASH_1 = Arrays.copyOf(new byte[] {1}, HASH_LENGTH_BYTES);
+    private static final byte[] TEST_HASH_2 = Arrays.copyOf(new byte[] {2}, HASH_LENGTH_BYTES);
+
+    @Test
+    public void testGetHash_returnsHash() {
+        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);
+
+        byte[] hash = chunkHash.getHash();
+
+        assertThat(hash).asList().containsExactlyElementsIn(Bytes.asList(TEST_HASH_1)).inOrder();
+    }
+
+    @Test
+    public void testEquals() {
+        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
+
+        assertThat(chunkHash1).isEqualTo(equalChunkHash1);
+        assertThat(chunkHash1).isNotEqualTo(chunkHash2);
+    }
+
+    @Test
+    public void testHashCode() {
+        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
+
+        int hash1 = chunkHash1.hashCode();
+        int equalHash1 = equalChunkHash1.hashCode();
+        int hash2 = chunkHash2.hashCode();
+
+        assertThat(hash1).isEqualTo(equalHash1);
+        assertThat(hash1).isNotEqualTo(hash2);
+    }
+
+    @Test
+    public void testCompareTo_whenEqual_returnsZero() {
+        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);
+        ChunkHash equalChunkHash = new ChunkHash(TEST_HASH_1);
+
+        int result = chunkHash.compareTo(equalChunkHash);
+
+        assertThat(result).isEqualTo(0);
+    }
+
+    @Test
+    public void testCompareTo_whenArgumentGreater_returnsNegative() {
+        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
+
+        int result = chunkHash1.compareTo(chunkHash2);
+
+        assertThat(result).isLessThan(0);
+    }
+
+    @Test
+    public void testCompareTo_whenArgumentSmaller_returnsPositive() {
+        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
+        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
+
+        int result = chunkHash2.compareTo(chunkHash1);
+
+        assertThat(result).isGreaterThan(0);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
new file mode 100644
index 0000000..c5f78c2
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import com.google.common.base.Charsets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkListingMapTest {
+    private static final ChunkHash CHUNK_A_HASH = getHash("CHUNK_A");
+    private static final ChunkHash CHUNK_B_HASH = getHash("CHUNK_B");
+    private static final ChunkHash CHUNK_C_HASH = getHash("CHUNK_C");
+
+    private static final int CHUNK_A_LENGTH = 256;
+    private static final int CHUNK_B_LENGTH = 1024;
+    private static final int CHUNK_C_LENGTH = 4055;
+
+    private static final int CHUNK_A_START = 0;
+    private static final int CHUNK_B_START = CHUNK_A_START + CHUNK_A_LENGTH;
+    private static final int CHUNK_C_START = CHUNK_B_START + CHUNK_B_LENGTH;
+
+    private ChunkListingMap mChunkListingMap;
+
+    @Before
+    public void setUp() {
+        mChunkListingMap = createFromFixture();
+    }
+
+    @Test
+    public void hasChunk_isTrueForExistingChunks() {
+        assertThat(mChunkListingMap.hasChunk(CHUNK_A_HASH)).isTrue();
+        assertThat(mChunkListingMap.hasChunk(CHUNK_B_HASH)).isTrue();
+        assertThat(mChunkListingMap.hasChunk(CHUNK_C_HASH)).isTrue();
+    }
+
+    @Test
+    public void hasChunk_isFalseForNonexistentChunks() {
+        assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse();
+        assertThat(mChunkListingMap.hasChunk(getHash(""))).isFalse();
+    }
+
+    @Test
+    public void getChunkListing_hasCorrectLengths() {
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getLength())
+                .isEqualTo(CHUNK_A_LENGTH);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getLength())
+                .isEqualTo(CHUNK_B_LENGTH);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getLength())
+                .isEqualTo(CHUNK_C_LENGTH);
+    }
+
+    @Test
+    public void getChunkListing_hasCorrectStarts() {
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getStart())
+                .isEqualTo(CHUNK_A_START);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getStart())
+                .isEqualTo(CHUNK_B_START);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getStart())
+                .isEqualTo(CHUNK_C_START);
+    }
+
+    @Test
+    public void getChunkListing_isNullForNonExistentChunks() {
+        assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).isNull();
+    }
+
+    private static ChunkListingMap createFromFixture() {
+        ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+        chunkListing.chunks = new ChunksMetadataProto.Chunk[3];
+        chunkListing.chunks[0] = newChunk(CHUNK_A_HASH.getHash(), CHUNK_A_LENGTH);
+        chunkListing.chunks[1] = newChunk(CHUNK_B_HASH.getHash(), CHUNK_B_LENGTH);
+        chunkListing.chunks[2] = newChunk(CHUNK_C_HASH.getHash(), CHUNK_C_LENGTH);
+        return ChunkListingMap.fromProto(chunkListing);
+    }
+
+    private static ChunkHash getHash(String name) {
+        return new ChunkHash(
+                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
+    }
+
+    public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) {
+        ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk();
+        newChunk.hash = Arrays.copyOf(hash, hash.length);
+        newChunk.length = length;
+        return newChunk;
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
new file mode 100644
index 0000000..c6b29b7
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class EncryptedChunkOrderingTest {
+    private static final byte[] TEST_BYTE_ARRAY_1 = new byte[] {1, 2, 3, 4, 5};
+    private static final byte[] TEST_BYTE_ARRAY_2 = new byte[] {5, 4, 3, 2, 1};
+
+    @Test
+    public void testEncryptedChunkOrdering_returnsValue() {
+        EncryptedChunkOrdering encryptedChunkOrdering =
+                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
+
+        byte[] bytes = encryptedChunkOrdering.encryptedChunkOrdering();
+
+        assertThat(bytes)
+                .asList()
+                .containsExactlyElementsIn(Bytes.asList(TEST_BYTE_ARRAY_1))
+                .inOrder();
+    }
+
+    @Test
+    public void testEquals() {
+        EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
+        EncryptedChunkOrdering equalChunkOrdering1 =
+                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
+        EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2);
+
+        assertThat(chunkOrdering1).isEqualTo(equalChunkOrdering1);
+        assertThat(chunkOrdering1).isNotEqualTo(chunkOrdering2);
+    }
+
+    @Test
+    public void testHashCode() {
+        EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
+        EncryptedChunkOrdering equalChunkOrdering1 =
+                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
+        EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2);
+
+        int hash1 = chunkOrdering1.hashCode();
+        int equalHash1 = equalChunkOrdering1.hashCode();
+        int hash2 = chunkOrdering2.hashCode();
+
+        assertThat(hash1).isEqualTo(equalHash1);
+        assertThat(hash1).isNotEqualTo(hash2);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java
new file mode 100644
index 0000000..590938e
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.AES_256_GCM;
+import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
+import static com.android.server.backup.testing.CryptoTestUtils.newChunk;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.testing.DiffScriptProcessor;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+import com.google.common.primitives.Longs;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class BackupFileBuilderTest {
+    private static final String TEST_DATA_1 =
+            "I'm already there or close to [T7-9/executive level] in terms of big-picture vision";
+    private static final String TEST_DATA_2 =
+            "I was known for Real Games and should have been brought in for advice";
+    private static final String TEST_DATA_3 =
+            "Pride is rooted in the delusional belief held by all humans in an unchanging self";
+
+    private static final byte[] TEST_FINGERPRINT_MIXER_SALT =
+            Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES);
+
+    private static final ChunkHash TEST_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {0}, EncryptedChunk.KEY_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1}, EncryptedChunk.KEY_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_3 =
+            new ChunkHash(Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES));
+
+    private static final byte[] TEST_NONCE =
+            Arrays.copyOf(new byte[] {3}, EncryptedChunk.NONCE_LENGTH_BYTES);
+
+    private static final EncryptedChunk TEST_CHUNK_1 =
+            EncryptedChunk.create(TEST_HASH_1, TEST_NONCE, TEST_DATA_1.getBytes(UTF_8));
+    private static final EncryptedChunk TEST_CHUNK_2 =
+            EncryptedChunk.create(TEST_HASH_2, TEST_NONCE, TEST_DATA_2.getBytes(UTF_8));
+    private static final EncryptedChunk TEST_CHUNK_3 =
+            EncryptedChunk.create(TEST_HASH_3, TEST_NONCE, TEST_DATA_3.getBytes(UTF_8));
+
+    private static final byte[] TEST_CHECKSUM = {1, 2, 3, 4, 5, 6};
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private File mOldFile;
+    private ChunksMetadataProto.ChunkListing mOldChunkListing;
+    private EncryptedChunkEncoder mEncryptedChunkEncoder;
+
+    @Before
+    public void setUp() {
+        mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder();
+    }
+
+    @Test
+    public void writeChunks_nonIncremental_writesCorrectRawData() throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        BackupFileBuilder backupFileBuilder = BackupFileBuilder.createForNonIncremental(output);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_1, TEST_HASH_2));
+
+        byte[] actual = output.toByteArray();
+        byte[] expected =
+                Bytes.concat(
+                        TEST_CHUNK_1.nonce(),
+                        TEST_CHUNK_1.encryptedBytes(),
+                        TEST_CHUNK_2.nonce(),
+                        TEST_CHUNK_2.encryptedBytes());
+        assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder();
+    }
+
+    @Test
+    public void writeChunks_nonIncrementalWithDuplicates_writesEachChunkOnlyOnce()
+            throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        BackupFileBuilder backupFileBuilder = BackupFileBuilder.createForNonIncremental(output);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_1),
+                getNewChunkMap(TEST_HASH_1, TEST_HASH_2));
+
+        byte[] actual = output.toByteArray();
+        byte[] expected =
+                Bytes.concat(
+                        TEST_CHUNK_1.nonce(),
+                        TEST_CHUNK_1.encryptedBytes(),
+                        TEST_CHUNK_2.nonce(),
+                        TEST_CHUNK_2.encryptedBytes());
+        assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder();
+    }
+
+    @Test
+    public void writeChunks_incremental_writesParsableDiffScript() throws Exception {
+        // We will insert chunk 2 in between chunks 1 and 3.
+        setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3));
+        ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream();
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3),
+                getNewChunkMap(TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        byte[] actual =
+                stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray()));
+        byte[] expected =
+                Bytes.concat(
+                        TEST_CHUNK_1.nonce(),
+                        TEST_CHUNK_1.encryptedBytes(),
+                        TEST_CHUNK_2.nonce(),
+                        TEST_CHUNK_2.encryptedBytes(),
+                        TEST_CHUNK_3.nonce(),
+                        TEST_CHUNK_3.encryptedBytes());
+        assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder();
+    }
+
+    @Test
+    public void writeChunks_incrementalWithDuplicates_writesEachChunkOnlyOnce() throws Exception {
+        // We will insert chunk 2 twice in between chunks 1 and 3.
+        setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3));
+        ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream();
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_2, TEST_HASH_3),
+                getNewChunkMap(TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        byte[] actual =
+                stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray()));
+        byte[] expected =
+                Bytes.concat(
+                        TEST_CHUNK_1.nonce(),
+                        TEST_CHUNK_1.encryptedBytes(),
+                        TEST_CHUNK_2.nonce(),
+                        TEST_CHUNK_2.encryptedBytes(),
+                        TEST_CHUNK_3.nonce(),
+                        TEST_CHUNK_3.encryptedBytes());
+        assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder();
+    }
+
+    @Test
+    public void writeChunks_writesChunksInOrderOfHash() throws Exception {
+        setUpOldBackupWithChunks(ImmutableList.of());
+        ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream();
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing);
+
+        // Write chunks out of order.
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_2, TEST_HASH_1),
+                getNewChunkMap(TEST_HASH_2, TEST_HASH_1));
+        backupFileBuilder.finish(getTestMetadata());
+
+        byte[] actual =
+                stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray()));
+        byte[] expected =
+                Bytes.concat(
+                        TEST_CHUNK_1.nonce(),
+                        TEST_CHUNK_1.encryptedBytes(),
+                        TEST_CHUNK_2.nonce(),
+                        TEST_CHUNK_2.encryptedBytes());
+        assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder();
+    }
+
+    @Test
+    public void writeChunks_alreadyFlushed_throwsException() throws Exception {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing());
+        backupFileBuilder.finish(getTestMetadata());
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> backupFileBuilder.writeChunks(ImmutableList.of(), getNewChunkMap()));
+    }
+
+    @Test
+    public void getNewChunkListing_hasChunksInOrderOfKey() throws Exception {
+        // We will insert chunk 2 in between chunks 1 and 3.
+        setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3));
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        // Write chunks out of order.
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkListing expected = expectedChunkListing();
+        ChunksMetadataProto.ChunkListing actual =
+                backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT);
+        assertListingsEqual(actual, expected);
+    }
+
+    @Test
+    public void getNewChunkListing_writeChunksInTwoBatches_returnsListingContainingAllChunks()
+            throws Exception {
+        // We will insert chunk 2 in between chunks 1 and 3.
+        setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3));
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2), getNewChunkMap(TEST_HASH_2));
+        backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_3), getNewChunkMap(TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkListing expected = expectedChunkListing();
+        ChunksMetadataProto.ChunkListing actual =
+                backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT);
+        assertListingsEqual(actual, expected);
+    }
+
+    @Test
+    public void getNewChunkListing_writeDuplicateChunks_writesEachChunkOnlyOnce() throws Exception {
+        // We will append [2][3][3][2] onto [1].
+        setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1));
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3),
+                getNewChunkMap(TEST_HASH_3, TEST_HASH_2));
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_3, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_3, TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkListing expected = expectedChunkListing();
+        ChunksMetadataProto.ChunkListing actual =
+                backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT);
+        assertListingsEqual(actual, expected);
+    }
+
+    @Test
+    public void getNewChunkListing_nonIncrementalWithNoSalt_doesNotThrowOnSerialisation() {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream());
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        // Does not throw.
+        ChunksMetadataProto.ChunkListing.toByteArray(newChunkListing);
+    }
+
+    @Test
+    public void getNewChunkListing_incrementalWithNoSalt_doesNotThrowOnSerialisation()
+            throws Exception {
+
+        setUpOldBackupWithChunks(ImmutableList.of());
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        // Does not throw.
+        ChunksMetadataProto.ChunkListing.toByteArray(newChunkListing);
+    }
+
+    @Test
+    public void getNewChunkListing_nonIncrementalWithNoSalt_hasEmptySalt() {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream());
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        assertThat(newChunkListing.fingerprintMixerSalt).isEmpty();
+    }
+
+    @Test
+    public void getNewChunkListing_incrementalWithNoSalt_hasEmptySalt() throws Exception {
+        setUpOldBackupWithChunks(ImmutableList.of());
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        assertThat(newChunkListing.fingerprintMixerSalt).isEmpty();
+    }
+
+    @Test
+    public void getNewChunkListing_nonIncrementalWithSalt_hasGivenSalt() {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream());
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT);
+
+        assertThat(newChunkListing.fingerprintMixerSalt).isEqualTo(TEST_FINGERPRINT_MIXER_SALT);
+    }
+
+    @Test
+    public void getNewChunkListing_incrementalWithSalt_hasGivenSalt() throws Exception {
+        setUpOldBackupWithChunks(ImmutableList.of());
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT);
+
+        assertThat(newChunkListing.fingerprintMixerSalt).isEqualTo(TEST_FINGERPRINT_MIXER_SALT);
+    }
+
+    @Test
+    public void getNewChunkListing_nonIncremental_hasCorrectCipherTypeAndChunkOrderingType() {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream());
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        assertThat(newChunkListing.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM);
+        assertThat(newChunkListing.chunkOrderingType)
+                .isEqualTo(ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED);
+    }
+
+    @Test
+    public void getNewChunkListing_incremental_hasCorrectCipherTypeAndChunkOrderingType()
+            throws Exception {
+        setUpOldBackupWithChunks(ImmutableList.of());
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), mOldChunkListing);
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null);
+
+        assertThat(newChunkListing.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM);
+        assertThat(newChunkListing.chunkOrderingType)
+                .isEqualTo(ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED);
+    }
+
+    @Test
+    public void getNewChunkOrdering_chunksHaveCorrectStartPositions() throws Exception {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing());
+
+        // Write out of order by key to check that ordering is maintained.
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkOrdering actual =
+                backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM);
+        // The chunks are listed in the order they are written above, but the start positions are
+        // determined by the order in the encrypted blob (which is lexicographical by key).
+        int chunk1Start = 0;
+        int chunk2Start =
+                chunk1Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1);
+        int chunk3Start =
+                chunk2Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2);
+
+        int[] expected = {chunk1Start, chunk3Start, chunk2Start};
+        assertThat(actual.starts.length).isEqualTo(expected.length);
+        for (int i = 0; i < actual.starts.length; i++) {
+            assertThat(expected[i]).isEqualTo(actual.starts[i]);
+        }
+    }
+
+    @Test
+    public void getNewChunkOrdering_duplicateChunks_writesDuplicates() throws Exception {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing());
+
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_1, TEST_HASH_2));
+        backupFileBuilder.writeChunks(
+                ImmutableList.of(TEST_HASH_3, TEST_HASH_3), getNewChunkMap(TEST_HASH_3));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkOrdering actual =
+                backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM);
+        int chunk1Start = 0;
+        int chunk2Start =
+                chunk1Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1);
+        int chunk3Start =
+                chunk2Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2);
+
+        int[] expected = {chunk1Start, chunk2Start, chunk2Start, chunk3Start, chunk3Start};
+        assertThat(actual.starts.length).isEqualTo(expected.length);
+        for (int i = 0; i < actual.starts.length; i++) {
+            assertThat(expected[i]).isEqualTo(actual.starts[i]);
+        }
+    }
+
+    @Test
+    public void getNewChunkOrdering_returnsOrderingWithChecksum() throws Exception {
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing());
+
+        backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_1), getNewChunkMap(TEST_HASH_1));
+        backupFileBuilder.finish(getTestMetadata());
+
+        ChunksMetadataProto.ChunkOrdering actual =
+                backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM);
+        assertThat(actual.checksum).isEqualTo(TEST_CHECKSUM);
+    }
+
+    @Test
+    public void finish_writesMetadata() throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        BackupFileBuilder builder = BackupFileBuilder.createForNonIncremental(output);
+        ChunksMetadataProto.ChunksMetadata expectedMetadata = getTestMetadata();
+
+        builder.finish(expectedMetadata);
+
+        // The output is [metadata]+[long giving size of metadata].
+        byte[] metadataBytes =
+                Arrays.copyOfRange(output.toByteArray(), 0, output.size() - Long.BYTES);
+        ChunksMetadataProto.ChunksMetadata actualMetadata =
+                ChunksMetadataProto.ChunksMetadata.parseFrom(metadataBytes);
+        assertThat(actualMetadata.checksumType).isEqualTo(ChunksMetadataProto.SHA_256);
+        assertThat(actualMetadata.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM);
+    }
+
+    @Test
+    public void finish_writesMetadataPosition() throws Exception {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        BackupFileBuilder builder = BackupFileBuilder.createForNonIncremental(output);
+
+        builder.writeChunks(
+                ImmutableList.of(TEST_HASH_1, TEST_HASH_2),
+                getNewChunkMap(TEST_HASH_1, TEST_HASH_2));
+        builder.writeChunks(ImmutableList.of(TEST_HASH_3), getNewChunkMap(TEST_HASH_3));
+        builder.finish(getTestMetadata());
+
+        long expectedPosition =
+                (long) mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1)
+                        + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2)
+                        + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_3);
+        long actualPosition =
+                Longs.fromByteArray(
+                        Arrays.copyOfRange(
+                                output.toByteArray(), output.size() - Long.BYTES, output.size()));
+        assertThat(actualPosition).isEqualTo(expectedPosition);
+    }
+
+    @Test
+    public void finish_flushesOutputStream() throws Exception {
+        OutputStream diffOutputStream = mock(OutputStream.class);
+        BackupFileBuilder backupFileBuilder =
+                BackupFileBuilder.createForIncremental(
+                        diffOutputStream, new ChunksMetadataProto.ChunkListing());
+
+        backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_1), getNewChunkMap(TEST_HASH_1));
+        diffOutputStream.flush();
+
+        verify(diffOutputStream).flush();
+    }
+
+    private void setUpOldBackupWithChunks(List<EncryptedChunk> chunks) throws Exception {
+        mOldFile = mTemporaryFolder.newFile();
+        ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+        chunkListing.fingerprintMixerSalt =
+                Arrays.copyOf(TEST_FINGERPRINT_MIXER_SALT, TEST_FINGERPRINT_MIXER_SALT.length);
+        chunkListing.cipherType = AES_256_GCM;
+        chunkListing.chunkOrderingType = CHUNK_ORDERING_TYPE_UNSPECIFIED;
+
+        List<ChunksMetadataProto.Chunk> knownChunks = new ArrayList<>();
+        try (FileOutputStream outputStream = new FileOutputStream(mOldFile)) {
+            for (EncryptedChunk chunk : chunks) {
+                // Chunks are encoded in the format [nonce]+[data].
+                outputStream.write(chunk.nonce());
+                outputStream.write(chunk.encryptedBytes());
+
+                knownChunks.add(createChunkFor(chunk));
+            }
+
+            outputStream.flush();
+        }
+
+        chunkListing.chunks = knownChunks.toArray(new ChunksMetadataProto.Chunk[0]);
+        mOldChunkListing = chunkListing;
+    }
+
+    private byte[] parseDiffScript(byte[] diffScript) throws Exception {
+        File newFile = mTemporaryFolder.newFile();
+        new DiffScriptProcessor(mOldFile, newFile).process(new ByteArrayInputStream(diffScript));
+        return Files.toByteArray(newFile);
+    }
+
+    private void assertListingsEqual(
+            ChunksMetadataProto.ChunkListing result, ChunksMetadataProto.ChunkListing expected) {
+        assertThat(result.chunks.length).isEqualTo(expected.chunks.length);
+        for (int i = 0; i < result.chunks.length; i++) {
+            assertWithMessage("Chunk " + i)
+                    .that(result.chunks[i].length)
+                    .isEqualTo(expected.chunks[i].length);
+            assertWithMessage("Chunk " + i)
+                    .that(result.chunks[i].hash)
+                    .isEqualTo(expected.chunks[i].hash);
+        }
+    }
+
+    private static ImmutableMap<ChunkHash, EncryptedChunk> getNewChunkMap(ChunkHash... hashes) {
+        ImmutableMap.Builder<ChunkHash, EncryptedChunk> builder = ImmutableMap.builder();
+        for (ChunkHash hash : hashes) {
+            if (TEST_HASH_1.equals(hash)) {
+                builder.put(TEST_HASH_1, TEST_CHUNK_1);
+            } else if (TEST_HASH_2.equals(hash)) {
+                builder.put(TEST_HASH_2, TEST_CHUNK_2);
+            } else if (TEST_HASH_3.equals(hash)) {
+                builder.put(TEST_HASH_3, TEST_CHUNK_3);
+            } else {
+                fail("Hash was not recognised: " + hash);
+            }
+        }
+        return builder.build();
+    }
+
+    private static ChunksMetadataProto.ChunksMetadata getTestMetadata() {
+        ChunksMetadataProto.ChunksMetadata metadata = new ChunksMetadataProto.ChunksMetadata();
+        metadata.checksumType = ChunksMetadataProto.SHA_256;
+        metadata.cipherType = AES_256_GCM;
+        return metadata;
+    }
+
+    private static byte[] stripMetadataAndPositionFromOutput(byte[] output) {
+        long metadataStart =
+                Longs.fromByteArray(
+                        Arrays.copyOfRange(output, output.length - Long.BYTES, output.length));
+        return Arrays.copyOfRange(output, 0, (int) metadataStart);
+    }
+
+    private ChunksMetadataProto.ChunkListing expectedChunkListing() {
+        ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+        chunkListing.fingerprintMixerSalt =
+                Arrays.copyOf(TEST_FINGERPRINT_MIXER_SALT, TEST_FINGERPRINT_MIXER_SALT.length);
+        chunkListing.cipherType = AES_256_GCM;
+        chunkListing.chunkOrderingType = CHUNK_ORDERING_TYPE_UNSPECIFIED;
+        chunkListing.chunks = new ChunksMetadataProto.Chunk[3];
+        chunkListing.chunks[0] = createChunkFor(TEST_CHUNK_1);
+        chunkListing.chunks[1] = createChunkFor(TEST_CHUNK_2);
+        chunkListing.chunks[2] = createChunkFor(TEST_CHUNK_3);
+        return chunkListing;
+    }
+
+    private ChunksMetadataProto.Chunk createChunkFor(EncryptedChunk encryptedChunk) {
+        byte[] chunkHash = encryptedChunk.key().getHash();
+        byte[] hashCopy = Arrays.copyOf(chunkHash, chunkHash.length);
+        return newChunk(hashCopy, mEncryptedChunkEncoder.getEncodedLengthOfChunk(encryptedChunk));
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
new file mode 100644
index 0000000..19e3b28
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkEncryptorTest {
+    private static final String MAC_ALGORITHM = "HmacSHA256";
+    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final int GCM_NONCE_LENGTH_BYTES = 12;
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+    private static final String CHUNK_PLAINTEXT =
+            "A little Learning is a dang'rous Thing;\n"
+                    + "Drink deep, or taste not the Pierian Spring:\n"
+                    + "There shallow Draughts intoxicate the Brain,\n"
+                    + "And drinking largely sobers us again.";
+    private static final byte[] PLAINTEXT_BYTES = CHUNK_PLAINTEXT.getBytes(UTF_8);
+    private static final byte[] NONCE_1 = "0123456789abc".getBytes(UTF_8);
+    private static final byte[] NONCE_2 = "123456789abcd".getBytes(UTF_8);
+
+    private static final byte[][] NONCES = new byte[][] {NONCE_1, NONCE_2};
+
+    @Mock private SecureRandom mSecureRandomMock;
+    private SecretKey mSecretKey;
+    private ChunkHash mPlaintextHash;
+    private ChunkEncryptor mChunkEncryptor;
+
+    @Before
+    public void setUp() throws Exception {
+        mSecretKey = generateAesKey();
+        ChunkHasher chunkHasher = new ChunkHasher(mSecretKey);
+        mPlaintextHash = chunkHasher.computeHash(PLAINTEXT_BYTES);
+        mSecureRandomMock = mock(SecureRandom.class);
+        mChunkEncryptor = new ChunkEncryptor(mSecretKey, mSecureRandomMock);
+
+        // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes().
+        doAnswer(
+                    new Answer<Void>() {
+                        private int mInvocation = 0;
+
+                        @Override
+                        public Void answer(InvocationOnMock invocation) {
+                            byte[] nonceDestination = invocation.getArgument(0);
+                            System.arraycopy(
+                                    NONCES[this.mInvocation],
+                                    0,
+                                    nonceDestination,
+                                    0,
+                                    GCM_NONCE_LENGTH_BYTES);
+                            this.mInvocation++;
+                            return null;
+                        }
+                    })
+                .when(mSecureRandomMock)
+                .nextBytes(any(byte[].class));
+    }
+
+    @Test
+    public void encrypt_withHash_resultContainsHashAsKey() throws Exception {
+        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        assertThat(chunk.key()).isEqualTo(mPlaintextHash);
+    }
+
+    @Test
+    public void encrypt_generatesHmacOfPlaintext() throws Exception {
+        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        byte[] generatedHash = chunk.key().getHash();
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mSecretKey);
+        byte[] plaintextHmac = mac.doFinal(PLAINTEXT_BYTES);
+        assertThat(generatedHash).isEqualTo(plaintextHmac);
+    }
+
+    @Test
+    public void encrypt_whenInvokedAgain_generatesNewNonce() throws Exception {
+        EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        assertThat(chunk1.nonce()).isNotEqualTo(chunk2.nonce());
+    }
+
+    @Test
+    public void encrypt_whenInvokedAgain_generatesNewCiphertext() throws Exception {
+        EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        assertThat(chunk1.encryptedBytes()).isNotEqualTo(chunk2.encryptedBytes());
+    }
+
+    @Test
+    public void encrypt_generates12ByteNonce() throws Exception {
+        EncryptedChunk encryptedChunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        byte[] nonce = encryptedChunk.nonce();
+        assertThat(nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+    }
+
+    @Test
+    public void encrypt_decryptedResultCorrespondsToPlaintext() throws Exception {
+        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(
+                Cipher.DECRYPT_MODE,
+                mSecretKey,
+                new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, chunk.nonce()));
+        byte[] decrypted = cipher.doFinal(chunk.encryptedBytes());
+        assertThat(decrypted).isEqualTo(PLAINTEXT_BYTES);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
new file mode 100644
index 0000000..72a927d
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkHasherTest {
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String MAC_ALGORITHM = "HmacSHA256";
+
+    private static final byte[] TEST_KEY = {100, 120};
+    private static final byte[] TEST_DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+
+    private SecretKey mSecretKey;
+    private ChunkHasher mChunkHasher;
+
+    @Before
+    public void setUp() throws Exception {
+        mSecretKey = new SecretKeySpec(TEST_KEY, KEY_ALGORITHM);
+        mChunkHasher = new ChunkHasher(mSecretKey);
+    }
+
+    @Test
+    public void computeHash_returnsHmacForData() throws Exception {
+        ChunkHash chunkHash = mChunkHasher.computeHash(TEST_DATA);
+
+        byte[] hash = chunkHash.getHash();
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mSecretKey);
+        byte[] expectedHash = mac.doFinal(TEST_DATA);
+        assertThat(hash).isEqualTo(expectedHash);
+    }
+
+    @Test
+    public void computeHash_generates256BitHmac() throws Exception {
+        int expectedLength = 256 / Byte.SIZE;
+
+        byte[] hash = mChunkHasher.computeHash(TEST_DATA).getHash();
+
+        assertThat(hash).hasLength(expectedLength);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
new file mode 100644
index 0000000..325b601
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class EncryptedChunkTest {
+    private static final byte[] CHUNK_HASH_1_BYTES =
+            Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES);
+    private static final byte[] NONCE_1 =
+            Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES);
+    private static final byte[] ENCRYPTED_BYTES_1 =
+            Arrays.copyOf(new byte[] {3}, EncryptedChunk.KEY_LENGTH_BYTES);
+
+    private static final byte[] CHUNK_HASH_2_BYTES =
+            Arrays.copyOf(new byte[] {4}, ChunkHash.HASH_LENGTH_BYTES);
+    private static final byte[] NONCE_2 =
+            Arrays.copyOf(new byte[] {5}, EncryptedChunk.NONCE_LENGTH_BYTES);
+    private static final byte[] ENCRYPTED_BYTES_2 =
+            Arrays.copyOf(new byte[] {6}, EncryptedChunk.KEY_LENGTH_BYTES);
+
+    @Test
+    public void testCreate_withIncorrectLength_throwsException() {
+        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+        byte[] shortNonce = Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES - 1);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> EncryptedChunk.create(chunkHash, shortNonce, ENCRYPTED_BYTES_1));
+    }
+
+    @Test
+    public void testEncryptedBytes_forNewlyCreatedObject_returnsCorrectValue() {
+        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+        EncryptedChunk encryptedChunk =
+                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+        byte[] returnedBytes = encryptedChunk.encryptedBytes();
+
+        assertThat(returnedBytes)
+                .asList()
+                .containsExactlyElementsIn(Bytes.asList(ENCRYPTED_BYTES_1))
+                .inOrder();
+    }
+
+    @Test
+    public void testKey_forNewlyCreatedObject_returnsCorrectValue() {
+        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+        EncryptedChunk encryptedChunk =
+                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+        ChunkHash returnedKey = encryptedChunk.key();
+
+        assertThat(returnedKey).isEqualTo(chunkHash);
+    }
+
+    @Test
+    public void testNonce_forNewlycreatedObject_returnCorrectValue() {
+        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
+        EncryptedChunk encryptedChunk =
+                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
+
+        byte[] returnedNonce = encryptedChunk.nonce();
+
+        assertThat(returnedNonce).asList().containsExactlyElementsIn(Bytes.asList(NONCE_1));
+    }
+
+    @Test
+    public void testEquals() {
+        ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+        ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+        ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
+        EncryptedChunk encryptedChunk1 =
+                EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+        EncryptedChunk equalEncryptedChunk1 =
+                EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+        EncryptedChunk encryptedChunk2 =
+                EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
+
+        assertThat(encryptedChunk1).isEqualTo(equalEncryptedChunk1);
+        assertThat(encryptedChunk1).isNotEqualTo(encryptedChunk2);
+    }
+
+    @Test
+    public void testHashCode() {
+        ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+        ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
+        ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
+        EncryptedChunk encryptedChunk1 =
+                EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+        EncryptedChunk equalEncryptedChunk1 =
+                EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
+        EncryptedChunk encryptedChunk2 =
+                EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
+
+        int hash1 = encryptedChunk1.hashCode();
+        int equalHash1 = equalEncryptedChunk1.hashCode();
+        int hash2 = encryptedChunk2.hashCode();
+
+        assertThat(hash1).isEqualTo(equalHash1);
+        assertThat(hash1).isNotEqualTo(hash2);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
new file mode 100644
index 0000000..7e1fded
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class InlineLengthsEncryptedChunkEncoderTest {
+
+    private static final byte[] TEST_NONCE =
+            Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
+    private static final byte[] TEST_KEY_DATA =
+            Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
+    private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
+
+    @Mock private BackupWriter mMockBackupWriter;
+    private ChunkHash mTestKey;
+    private EncryptedChunk mTestChunk;
+    private EncryptedChunkEncoder mEncoder;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockBackupWriter = mock(BackupWriter.class);
+        mTestKey = new ChunkHash(TEST_KEY_DATA);
+        mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
+        mEncoder = new InlineLengthsEncryptedChunkEncoder();
+    }
+
+    @Test
+    public void writeChunkToWriter_writesLengthThenNonceThenData() throws Exception {
+        mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
+
+        InOrder inOrder = inOrder(mMockBackupWriter);
+        inOrder.verify(mMockBackupWriter)
+                .writeBytes(
+                        InlineLengthsEncryptedChunkEncoder.toByteArray(
+                                TEST_NONCE.length + TEST_DATA.length));
+        inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
+        inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
+    }
+
+    @Test
+    public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
+        int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
+
+        assertThat(encodedLength).isEqualTo(Integer.BYTES + TEST_NONCE.length + TEST_DATA.length);
+    }
+
+    @Test
+    public void getChunkOrderingType_returnsExplicitStartsType() {
+        assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.INLINE_LENGTHS);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
new file mode 100644
index 0000000..6f58ee1
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class LengthlessEncryptedChunkEncoderTest {
+    private static final byte[] TEST_NONCE =
+            Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
+    private static final byte[] TEST_KEY_DATA =
+            Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
+    private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
+
+    @Mock private BackupWriter mMockBackupWriter;
+    private ChunkHash mTestKey;
+    private EncryptedChunk mTestChunk;
+    private EncryptedChunkEncoder mEncoder;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockBackupWriter = mock(BackupWriter.class);
+        mTestKey = new ChunkHash(TEST_KEY_DATA);
+        mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
+        mEncoder = new LengthlessEncryptedChunkEncoder();
+    }
+
+    @Test
+    public void writeChunkToWriter_writesNonceThenData() throws Exception {
+        mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
+
+        InOrder inOrder = inOrder(mMockBackupWriter);
+        inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
+        inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
+    }
+
+    @Test
+    public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
+        int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
+
+        assertThat(encodedLength).isEqualTo(TEST_NONCE.length + TEST_DATA.length);
+    }
+
+    @Test
+    public void getChunkOrderingType_returnsExplicitStartsType() {
+        assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.EXPLICIT_STARTS);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
new file mode 100644
index 0000000..966d3e2
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.google.common.primitives.Bytes;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayOutputStream;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class RawBackupWriterTest {
+    private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6};
+
+    private BackupWriter mWriter;
+    private ByteArrayOutputStream mOutput;
+
+    @Before
+    public void setUp() {
+        mOutput = new ByteArrayOutputStream();
+        mWriter = new RawBackupWriter(mOutput);
+    }
+
+    @Test
+    public void writeBytes_writesToOutputStream() throws Exception {
+        mWriter.writeBytes(TEST_BYTES);
+
+        assertThat(mOutput.toByteArray())
+                .asList()
+                .containsExactlyElementsIn(Bytes.asList(TEST_BYTES))
+                .inOrder();
+    }
+
+    @Test
+    public void writeChunk_throwsUnsupportedOperationException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> mWriter.writeChunk(0, 0));
+    }
+
+    @Test
+    public void getBytesWritten_returnsTotalSum() throws Exception {
+        mWriter.writeBytes(TEST_BYTES);
+        mWriter.writeBytes(TEST_BYTES);
+
+        long bytesWritten = mWriter.getBytesWritten();
+
+        assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length);
+    }
+
+    @Test
+    public void flush_flushesOutputStream() throws Exception {
+        mOutput = mock(ByteArrayOutputStream.class);
+        mWriter = new RawBackupWriter(mOutput);
+
+        mWriter.flush();
+
+        verify(mOutput).flush();
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
new file mode 100644
index 0000000..b607404
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/** Key wrapping tests */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyWrapUtilsTest {
+    private static final int KEY_SIZE_BITS = 256;
+    private static final int BITS_PER_BYTE = 8;
+    private static final int GCM_NONCE_LENGTH_BYTES = 16;
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+    /** Test a wrapped key has metadata */
+    @Test
+    public void wrap_addsMetadata() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.metadata).isNotNull();
+        assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM);
+    }
+
+    /** Test a wrapped key has an algorithm specified */
+    @Test
+    public void wrap_addsWrapAlgorithm() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM);
+    }
+
+    /** Test a wrapped key haas an nonce of the right length */
+    @Test
+    public void wrap_addsNonceOfAppropriateLength() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+    }
+
+    /** Test a wrapped key has a key of the right length */
+    @Test
+    public void wrap_addsTagOfAppropriateLength() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES);
+    }
+
+    /** Ensure a key can be wrapped and unwrapped again */
+    @Test
+    public void unwrap_unwrapsEncryptedKey() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        SecretKey tertiaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey);
+        SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure the unwrap method rejects keys with bad algorithms */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadWrapAlgorithm() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadKeyAlgorithm() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure the unwrap method rejects wrapped keys missing the metadata */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForMissingMetadata() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.metadata = null;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure unwrap rejects invalid secondary keys */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadSecondaryKey() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+
+        KeyWrapUtils.unwrap(generateAesKey(), wrappedKey);
+    }
+
+    /** Ensure rewrap can rewrap keys */
+    @Test
+    public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception {
+        SecretKey tertiaryKey = generateAesKey();
+        SecretKey oldSecondaryKey = generateAesKey();
+        SecretKey newSecondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedWithNew =
+                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+        assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure rewrap doesn't create something decryptable by an old key */
+    @Test(expected = InvalidKeyException.class)
+    public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception {
+        SecretKey tertiaryKey = generateAesKey();
+        SecretKey oldSecondaryKey = generateAesKey();
+        SecretKey newSecondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedWithNew =
+                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+        KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java
new file mode 100644
index 0000000..004f809
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+import java.security.KeyException;
+import java.security.SecureRandom;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Test the restore key fetcher */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class RestoreKeyFetcherTest {
+
+    private static final String KEY_GENERATOR_ALGORITHM = "AES";
+
+    private static final String TEST_SECONDARY_KEY_ALIAS = "test_2ndary_key";
+    private static final byte[] TEST_SECONDARY_KEY_BYTES = new byte[256 / Byte.SIZE];
+
+    @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+
+    /** Initialise the mocks **/
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /** Ensure the unwrap method works as expected */
+    @Test
+    public void unwrapTertiaryKey_returnsUnwrappedKey() throws Exception {
+        RecoverableKeyStoreSecondaryKey secondaryKey = createSecondaryKey();
+        SecretKey tertiaryKey = createTertiaryKey();
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(secondaryKey.getSecretKey(), tertiaryKey);
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS))
+                .thenReturn(Optional.of(secondaryKey));
+
+        SecretKey actualTertiaryKey =
+                RestoreKeyFetcher.unwrapTertiaryKey(
+                        () -> mSecondaryKeyManager,
+                        TEST_SECONDARY_KEY_ALIAS,
+                        wrappedTertiaryKey);
+
+        assertThat(actualTertiaryKey).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure that missing secondary keys are detected and an appropriate exception is thrown */
+    @Test
+    public void unwrapTertiaryKey_missingSecondaryKey_throwsSpecificException() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey());
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS)).thenReturn(Optional.empty());
+
+        assertThrows(
+                KeyException.class,
+                () ->
+                        RestoreKeyFetcher.unwrapTertiaryKey(
+                                () -> mSecondaryKeyManager,
+                                TEST_SECONDARY_KEY_ALIAS,
+                                wrappedTertiaryKey));
+    }
+
+    /** Ensure that invalid secondary keys are detected and an appropriate exception is thrown */
+    @Test
+    public void unwrapTertiaryKey_badSecondaryKey_throws() throws Exception {
+        RecoverableKeyStoreSecondaryKey badSecondaryKey =
+                new RecoverableKeyStoreSecondaryKey(
+                        TEST_SECONDARY_KEY_ALIAS,
+                        new SecretKeySpec(new byte[] {0, 1}, KEY_GENERATOR_ALGORITHM));
+
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey());
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS))
+                .thenReturn(Optional.of(badSecondaryKey));
+
+        assertThrows(
+                InvalidKeyException.class,
+                () ->
+                        RestoreKeyFetcher.unwrapTertiaryKey(
+                                () -> mSecondaryKeyManager,
+                                TEST_SECONDARY_KEY_ALIAS,
+                                wrappedTertiaryKey));
+    }
+
+    private static RecoverableKeyStoreSecondaryKey createSecondaryKey() {
+        return new RecoverableKeyStoreSecondaryKey(
+                TEST_SECONDARY_KEY_ALIAS,
+                new SecretKeySpec(TEST_SECONDARY_KEY_BYTES, KEY_GENERATOR_ALGORITHM));
+    }
+
+    private static SecretKey createTertiaryKey() {
+        return new TertiaryKeyGenerator(new SecureRandom(new byte[] {0})).generate();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
new file mode 100644
index 0000000..c31d19d
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.io.File;
+import java.time.Clock;
+
+@Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class)
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class SecondaryKeyRotationSchedulerTest {
+    private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+    @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    @Mock private Clock mClock;
+
+    private CryptoSettings mCryptoSettings;
+    private SecondaryKeyRotationScheduler mScheduler;
+    private long mRotationIntervalMillis;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context application = ApplicationProvider.getApplicationContext();
+
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(application);
+        mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+
+        mScheduler =
+                new SecondaryKeyRotationScheduler(
+                        application, mSecondaryKeyManager, mCryptoSettings, mClock);
+        ShadowStartSecondaryKeyRotationTask.reset();
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() {
+        long lastRotated = 100009;
+        long now = lastRotated + mRotationIntervalMillis;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfClockHasChanged() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception {
+        File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH);
+        file.createNewFile();
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() {
+        long lastRotated = 100009;
+        long now = lastRotated - 1;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    @Test
+    public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse();
+    }
+
+    @Test
+    public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated);
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() {
+        long now = 13295436;
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    private void setNow(long timestamp) {
+        when(mClock.millis()).thenReturn(timestamp);
+    }
+
+    @Implements(StartSecondaryKeyRotationTask.class)
+    public static class ShadowStartSecondaryKeyRotationTask {
+        private static boolean sRan = false;
+
+        @Implementation
+        public void run() {
+            sRan = true;
+        }
+
+        @Resetter
+        public static void reset() {
+            sRan = false;
+        }
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java
new file mode 100644
index 0000000..1ed8309
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.inOrder;
+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 static org.robolectric.RuntimeEnvironment.application;
+
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.security.SecureRandom;
+
+import javax.crypto.SecretKey;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowRecoveryController.class)
+public class TertiaryKeyManagerTest {
+
+    private static final String TEST_PACKAGE_1 = "com.example.app1";
+    private static final String TEST_PACKAGE_2 = "com.example.app2";
+
+    private SecureRandom mSecureRandom;
+    private RecoverableKeyStoreSecondaryKey mSecondaryKey;
+
+    @Mock private TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mSecureRandom = new SecureRandom();
+        mSecondaryKey =
+                new RecoverableKeyStoreSecondaryKeyManager(
+                                RecoveryController.getInstance(application), mSecureRandom)
+                        .generate();
+        ShadowRecoveryController.reset();
+    }
+
+    private TertiaryKeyManager createNewManager(String packageName) {
+        return new TertiaryKeyManager(
+                application,
+                mSecureRandom,
+                mTertiaryKeyRotationScheduler,
+                mSecondaryKey,
+                packageName);
+    }
+
+    @Test
+    public void getKey_noExistingKey_returnsNewKey() throws Exception {
+        assertThat(createNewManager(TEST_PACKAGE_1).getKey()).isNotNull();
+    }
+
+    @Test
+    public void getKey_noExistingKey_recordsIncrementalBackup() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+    }
+
+    @Test
+    public void getKey_existingKey_returnsExistingKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        SecretKey existingKey = manager.getKey();
+
+        assertThat(manager.getKey()).isEqualTo(existingKey);
+    }
+
+    @Test
+    public void getKey_existingKey_recordsBackupButNotRotation() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        reset(mTertiaryKeyRotationScheduler);
+
+        createNewManager(TEST_PACKAGE_1).getKey();
+
+        verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+        verify(mTertiaryKeyRotationScheduler, never()).recordKeyRotation(any());
+    }
+
+    @Test
+    public void getKey_existingKeyButRotationRequired_returnsNewKey() throws Exception {
+        SecretKey firstKey = createNewManager(TEST_PACKAGE_1).getKey();
+        when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true);
+
+        SecretKey secondKey = createNewManager(TEST_PACKAGE_1).getKey();
+
+        assertThat(secondKey).isNotEqualTo(firstKey);
+    }
+
+    @Test
+    public void getKey_existingKeyButRotationRequired_recordsKeyRotationAndBackup()
+            throws Exception {
+        when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true);
+        createNewManager(TEST_PACKAGE_1).getKey();
+
+        InOrder inOrder = inOrder(mTertiaryKeyRotationScheduler);
+        inOrder.verify(mTertiaryKeyRotationScheduler).recordKeyRotation(TEST_PACKAGE_1);
+        inOrder.verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+    }
+
+    @Test
+    public void getKey_twoApps_returnsDifferentKeys() throws Exception {
+        TertiaryKeyManager firstManager = createNewManager(TEST_PACKAGE_1);
+        TertiaryKeyManager secondManager = createNewManager(TEST_PACKAGE_2);
+        SecretKey firstKey = firstManager.getKey();
+
+        assertThat(secondManager.getKey()).isNotEqualTo(firstKey);
+    }
+
+    @Test
+    public void getWrappedKey_noExistingKey_returnsWrappedNewKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        SecretKey unwrappedKey = manager.getKey();
+        WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey();
+
+        SecretKey expectedUnwrappedKey =
+                KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey);
+    }
+
+    @Test
+    public void getWrappedKey_existingKey_returnsWrappedExistingKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey();
+        SecretKey unwrappedKey = manager.getKey();
+
+        SecretKey expectedUnwrappedKey =
+                KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey);
+    }
+
+    @Test
+    public void wasKeyRotated_noExistingKey_returnsTrue() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        assertThat(manager.wasKeyRotated()).isTrue();
+    }
+
+    @Test
+    public void wasKeyRotated_existingKey_returnsFalse() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        assertThat(createNewManager(TEST_PACKAGE_1).wasKeyRotated()).isFalse();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java
new file mode 100644
index 0000000..dfc7e2b
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.File;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for the tertiary key rotation scheduler */
+@RunWith(RobolectricTestRunner.class)
+public final class TertiaryKeyRotationSchedulerTest {
+
+    private static final int MAXIMUM_ROTATIONS_PER_WINDOW = 2;
+    private static final int MAX_BACKUPS_TILL_ROTATION = 31;
+    private static final String SHARED_PREFS_NAME = "tertiary_key_rotation_tracker";
+    private static final String PACKAGE_1 = "com.android.example1";
+    private static final String PACKAGE_2 = "com.android.example2";
+
+    @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @Mock private Clock mClock;
+
+    private File mFile;
+    private TertiaryKeyRotationScheduler mScheduler;
+
+    /** Setup the scheduler for test */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mFile = temporaryFolder.newFile();
+        mScheduler =
+                new TertiaryKeyRotationScheduler(
+                        new TertiaryKeyRotationTracker(
+                                application.getSharedPreferences(
+                                        SHARED_PREFS_NAME, Context.MODE_PRIVATE),
+                                MAX_BACKUPS_TILL_ROTATION),
+                        new TertiaryKeyRotationWindowedCount(mFile, mClock),
+                        MAXIMUM_ROTATIONS_PER_WINDOW);
+    }
+
+    /** Test we don't trigger a rotation straight off */
+    @Test
+    public void isKeyRotationDue_isFalseInitially() {
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test we don't prematurely trigger a rotation */
+    @Test
+    public void isKeyRotationDue_isFalseAfterInsufficientBackups() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION - 1);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test we do trigger a backup */
+    @Test
+    public void isKeyRotationDue_isTrueAfterEnoughBackups() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test rotation will occur if the quota allows */
+    @Test
+    public void isKeyRotationDue_isTrueIfRotationQuotaRemainsInWindow() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test rotation is blocked if the quota has been exhausted */
+    @Test
+    public void isKeyRotationDue_isFalseIfEnoughRotationsHaveHappenedInWindow() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test rotation is due after one window has passed */
+    @Test
+    public void isKeyRotationDue_isTrueAfterAWholeWindowHasPassed() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test the rotation state changes after a rotation */
+    @Test
+    public void isKeyRotationDue_isFalseAfterRotation() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_1);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test the rate limiting for a given window */
+    @Test
+    public void isKeyRotationDue_neverAllowsMoreThanInWindow() {
+        List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION);
+
+        // simulate backups of all apps each night
+        for (int i = 0; i < 300; i++) {
+            setTimeMillis(i * TimeUnit.HOURS.toMillis(24));
+            int rotationsThisNight = 0;
+            for (String app : apps) {
+                if (mScheduler.isKeyRotationDue(app)) {
+                    rotationsThisNight++;
+                    mScheduler.recordKeyRotation(app);
+                } else {
+                    mScheduler.recordBackup(app);
+                }
+            }
+            assertThat(rotationsThisNight).isAtMost(MAXIMUM_ROTATIONS_PER_WINDOW);
+        }
+    }
+
+    /** Test that backups are staggered over the window */
+    @Test
+    public void isKeyRotationDue_naturallyStaggersBackupsOverTime() {
+        List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION);
+
+        HashMap<String, ArrayList<Integer>> rotationDays = new HashMap<>();
+        for (String app : apps) {
+            rotationDays.put(app, new ArrayList<>());
+        }
+
+        // simulate backups of all apps each night
+        for (int i = 0; i < 300; i++) {
+            setTimeMillis(i * TimeUnit.HOURS.toMillis(24));
+            for (String app : apps) {
+                if (mScheduler.isKeyRotationDue(app)) {
+                    rotationDays.get(app).add(i);
+                    mScheduler.recordKeyRotation(app);
+                } else {
+                    mScheduler.recordBackup(app);
+                }
+            }
+        }
+
+        for (String app : apps) {
+            List<Integer> days = rotationDays.get(app);
+            for (int i = 1; i < days.size(); i++) {
+                assertThat(days.get(i) - days.get(i - 1)).isEqualTo(MAX_BACKUPS_TILL_ROTATION + 1);
+            }
+        }
+    }
+
+    private ArrayList<String> makeTestApps(int n) {
+        ArrayList<String> apps = new ArrayList<>();
+        for (int i = 0; i < n; i++) {
+            apps.add(String.format(Locale.US, "com.android.app%d", i));
+        }
+        return apps;
+    }
+
+    private void simulateBackups(int numberOfBackups) {
+        while (numberOfBackups > 0) {
+            mScheduler.recordBackup(PACKAGE_1);
+            numberOfBackups--;
+        }
+    }
+
+    private void setTimeMillis(long timeMillis) {
+        when(mClock.millis()).thenReturn(timeMillis);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java
new file mode 100644
index 0000000..bd30977
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Clock;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link TertiaryKeyRotationWindowedCount}. */
+@RunWith(RobolectricTestRunner.class)
+public class TertiaryKeyRotationWindowedCountTest {
+    private static final int TIMESTAMP_SIZE_IN_BYTES = 8;
+
+    @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Mock private Clock mClock;
+
+    private File mFile;
+    private TertiaryKeyRotationWindowedCount mWindowedcount;
+
+    /** Setup the windowed counter for testing */
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+        mFile = mTemporaryFolder.newFile();
+        mWindowedcount = new TertiaryKeyRotationWindowedCount(mFile, mClock);
+    }
+
+    /** Test handling bad files */
+    @Test
+    public void constructor_doesNotFailForBadFile() throws IOException {
+        new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock);
+    }
+
+    /** Test the count is 0 to start */
+    @Test
+    public void getCount_isZeroInitially() {
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Test the count is correct for a time window */
+    @Test
+    public void getCount_includesResultsInLastTwentyFourHours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(4));
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(23));
+        mWindowedcount.record();
+        mWindowedcount.record();
+        assertThat(mWindowedcount.getCount()).isEqualTo(4);
+    }
+
+    /** Test old results are ignored */
+    @Test
+    public void getCount_ignoresResultsOlderThanTwentyFourHours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Test future events are removed if the clock moves backways (e.g. DST, TZ change) */
+    @Test
+    public void getCount_removesFutureEventsIfClockHasChanged() {
+        setTimeMillis(1000);
+        mWindowedcount.record();
+        setTimeMillis(0);
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Check recording doesn't fail for a bad file */
+    @Test
+    public void record_doesNotFailForBadFile() throws Exception {
+        new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock).record();
+    }
+
+    /** Checks the state is persisted */
+    @Test
+    public void record_persistsStateToDisk() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        assertThat(new TertiaryKeyRotationWindowedCount(mFile, mClock).getCount()).isEqualTo(1);
+    }
+
+    /** Test the file doesn't contain unnecessary data */
+    @Test
+    public void record_compactsFileToLast24Hours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(TIMESTAMP_SIZE_IN_BYTES);
+        setTimeMillis(1);
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES);
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES);
+    }
+
+    private void setTimeMillis(long timeMillis) {
+        when(mClock.millis()).thenReturn(timeMillis);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java
new file mode 100644
index 0000000..ccc5f32
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+
+import android.content.Context;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.security.InvalidKeyException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+
+/** Tests for the tertiary key store */
+@RunWith(RobolectricTestRunner.class)
+public class TertiaryKeyStoreTest {
+
+    private static final String SECONDARY_KEY_ALIAS = "Robbo/Ranx";
+
+    private Context mApplication;
+    private TertiaryKeyStore mTertiaryKeyStore;
+    private SecretKey mSecretKey;
+
+    /** Initialise the keystore for testing */
+    @Before
+    public void setUp() throws Exception {
+        mApplication = RuntimeEnvironment.application;
+        mSecretKey = generateAesKey();
+        mTertiaryKeyStore =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, mSecretKey));
+    }
+
+    /** Test a reound trip for a key */
+    @Test
+    public void load_loadsAKeyThatWasSaved() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+
+        Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
+
+        assertTrue(maybeLoadedKey.isPresent());
+        assertEquals(packageKey, maybeLoadedKey.get());
+    }
+
+    /** Test isolation between packages */
+    @Test
+    public void load_doesNotLoadAKeyForAnotherSecondary() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+        TertiaryKeyStore managerWithOtherSecondaryKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(
+                                "myNewSecondaryKeyAlias", generateAesKey()));
+
+        assertFalse(managerWithOtherSecondaryKey.load(packageName).isPresent());
+    }
+
+    /** Test non-existent key handling */
+    @Test
+    public void load_returnsAbsentForANonExistentKey() throws Exception {
+        assertFalse(mTertiaryKeyStore.load("mystery.package").isPresent());
+    }
+
+    /** Test handling incorrect keys */
+    @Test
+    public void load_throwsIfHasWrongBackupKey() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+        TertiaryKeyStore managerWithBadKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, generateAesKey()));
+
+        assertThrows(InvalidKeyException.class, () -> managerWithBadKey.load(packageName));
+    }
+
+    /** Test handling of empty app name */
+    @Test
+    public void load_throwsForEmptyApplicationName() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> mTertiaryKeyStore.load(""));
+    }
+
+    /** Test handling of an invalid app name */
+    @Test
+    public void load_throwsForBadApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mTertiaryKeyStore.load("com/android/example"));
+    }
+
+    /** Test key replacement */
+    @Test
+    public void save_overwritesPreviousKey() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey oldKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, oldKey);
+        SecretKey newKey = generateAesKey();
+
+        mTertiaryKeyStore.save(packageName, newKey);
+
+        Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
+        assertTrue(maybeLoadedKey.isPresent());
+        SecretKey loadedKey = maybeLoadedKey.get();
+        assertThat(loadedKey).isNotEqualTo(oldKey);
+        assertThat(loadedKey).isEqualTo(newKey);
+    }
+
+    /** Test saving with an empty application name fails */
+    @Test
+    public void save_throwsForEmptyApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class, () -> mTertiaryKeyStore.save("", generateAesKey()));
+    }
+
+    /** Test saving an invalid application name fails */
+    @Test
+    public void save_throwsForBadApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mTertiaryKeyStore.save("com/android/example", generateAesKey()));
+    }
+
+    /** Test handling an empty database */
+    @Test
+    public void getAll_returnsEmptyMapForEmptyDb() throws Exception {
+        assertThat(mTertiaryKeyStore.getAll()).isEmpty();
+    }
+
+    /** Test loading all available keys works as expected */
+    @Test
+    public void getAll_returnsAllKeysSaved() throws Exception {
+        String package1 = "com.android.example";
+        SecretKey key1 = generateAesKey();
+        String package2 = "com.anndroid.example1";
+        SecretKey key2 = generateAesKey();
+        String package3 = "com.android.example2";
+        SecretKey key3 = generateAesKey();
+        mTertiaryKeyStore.save(package1, key1);
+        mTertiaryKeyStore.save(package2, key2);
+        mTertiaryKeyStore.save(package3, key3);
+
+        Map<String, SecretKey> keys = mTertiaryKeyStore.getAll();
+
+        assertThat(keys).containsExactly(package1, key1, package2, key2, package3, key3);
+    }
+
+    /** Test cross-secondary isolation */
+    @Test
+    public void getAll_doesNotReturnKeysForOtherSecondary() throws Exception {
+        String packageName = "com.android.example";
+        TertiaryKeyStore managerWithOtherSecondaryKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(
+                                "myNewSecondaryKeyAlias", generateAesKey()));
+        managerWithOtherSecondaryKey.save(packageName, generateAesKey());
+
+        assertThat(mTertiaryKeyStore.getAll()).isEmpty();
+    }
+
+    /** Test mass put into the keystore */
+    @Test
+    public void putAll_putsAllWrappedKeysInTheStore() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey key = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(mSecretKey, key);
+
+        Map<String, WrappedKeyProto.WrappedKey> testElements = new HashMap<>();
+        testElements.put(packageName, wrappedKey);
+        mTertiaryKeyStore.putAll(testElements);
+
+        assertThat(mTertiaryKeyStore.getAll()).containsKey(packageName);
+        assertThat(mTertiaryKeyStore.getAll().get(packageName).getEncoded())
+                .isEqualTo(key.getEncoded());
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
new file mode 100644
index 0000000..215e1cb
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Debug;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class DecryptedChunkKvOutputTest {
+    private static final String TEST_KEY_1 = "key_1";
+    private static final String TEST_KEY_2 = "key_2";
+    private static final byte[] TEST_VALUE_1 = {1, 2, 3};
+    private static final byte[] TEST_VALUE_2 = {10, 11, 12, 13};
+    private static final byte[] TEST_PAIR_1 = toByteArray(createPair(TEST_KEY_1, TEST_VALUE_1));
+    private static final byte[] TEST_PAIR_2 = toByteArray(createPair(TEST_KEY_2, TEST_VALUE_2));
+    private static final int TEST_BUFFER_SIZE = Math.max(TEST_PAIR_1.length, TEST_PAIR_2.length);
+
+    @Mock private ChunkHasher mChunkHasher;
+    private DecryptedChunkKvOutput mOutput;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mChunkHasher.computeHash(any()))
+                .thenAnswer(invocation -> fakeHash(invocation.getArgument(0)));
+        mOutput = new DecryptedChunkKvOutput(mChunkHasher);
+    }
+
+    @Test
+    public void open_returnsInstance() throws Exception {
+        assertThat(mOutput.open()).isEqualTo(mOutput);
+    }
+
+    @Test
+    public void processChunk_alreadyClosed_throws() throws Exception {
+        mOutput.open();
+        mOutput.close();
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mOutput.processChunk(TEST_PAIR_1, TEST_PAIR_1.length));
+    }
+
+    @Test
+    public void getDigest_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getDigest());
+    }
+
+    @Test
+    public void getDigest_returnsDigestOfSortedHashes() throws Exception {
+        mOutput.open();
+        Debug.waitForDebugger();
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.close();
+
+        byte[] actualDigest = mOutput.getDigest();
+
+        MessageDigest digest = MessageDigest.getInstance(DecryptedChunkKvOutput.DIGEST_ALGORITHM);
+        Stream.of(TEST_PAIR_1, TEST_PAIR_2)
+                .map(DecryptedChunkKvOutputTest::fakeHash)
+                .sorted(Comparator.naturalOrder())
+                .forEachOrdered(hash -> digest.update(hash.getHash()));
+        assertThat(actualDigest).isEqualTo(digest.digest());
+    }
+
+    @Test
+    public void getPairs_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getPairs());
+    }
+
+    @Test
+    public void getPairs_returnsPairsSortedByKey() throws Exception {
+        mOutput.open();
+        // Write out of order to check that it sorts the chunks.
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.close();
+
+        List<KeyValuePairProto.KeyValuePair> pairs = mOutput.getPairs();
+
+        assertThat(
+                        isInOrder(
+                                pairs,
+                                Comparator.comparing(
+                                        (KeyValuePairProto.KeyValuePair pair) -> pair.key)))
+                .isTrue();
+        assertThat(pairs).hasSize(2);
+        assertThat(pairs.get(0).key).isEqualTo(TEST_KEY_1);
+        assertThat(pairs.get(0).value).isEqualTo(TEST_VALUE_1);
+        assertThat(pairs.get(1).key).isEqualTo(TEST_KEY_2);
+        assertThat(pairs.get(1).value).isEqualTo(TEST_VALUE_2);
+    }
+
+    private static KeyValuePairProto.KeyValuePair createPair(String key, byte[] value) {
+        KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair();
+        pair.key = key;
+        pair.value = value;
+        return pair;
+    }
+
+    private boolean isInOrder(
+            List<KeyValuePairProto.KeyValuePair> list,
+            Comparator<KeyValuePairProto.KeyValuePair> comparator) {
+        if (list.size() < 2) {
+            return true;
+        }
+
+        List<KeyValuePairProto.KeyValuePair> sortedList = new ArrayList<>(list);
+        Collections.sort(sortedList, comparator);
+        return list.equals(sortedList);
+    }
+
+    private static byte[] toByteArray(KeyValuePairProto.KeyValuePair nano) {
+        return KeyValuePairProto.KeyValuePair.toByteArray(nano);
+    }
+
+    private static ChunkHash fakeHash(byte[] data) {
+        return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES));
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
new file mode 100644
index 0000000..acc6628
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyValueListingBuilderTest {
+    private static final String TEST_KEY_1 = "test_key_1";
+    private static final String TEST_KEY_2 = "test_key_2";
+    private static final ChunkHash TEST_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1, 2}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {5, 6}, ChunkHash.HASH_LENGTH_BYTES));
+
+    private KeyValueListingBuilder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new KeyValueListingBuilder();
+    }
+
+    @Test
+    public void addPair_nullKey_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(null, TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_emptyKey_throws() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.addPair("", TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_nullHash_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(TEST_KEY_1, null));
+    }
+
+    @Test
+    public void build_noPairs_buildsEmptyListing() {
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries).isEmpty();
+    }
+
+    @Test
+    public void build_returnsCorrectListing() {
+        mBuilder.addPair(TEST_KEY_1, TEST_HASH_1);
+
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(1);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+    }
+
+    @Test
+    public void addAll_addsAllPairsInMap() {
+        ImmutableMap<String, ChunkHash> pairs =
+                new ImmutableMap.Builder<String, ChunkHash>()
+                        .put(TEST_KEY_1, TEST_HASH_1)
+                        .put(TEST_KEY_2, TEST_HASH_2)
+                        .build();
+
+        mBuilder.addAll(pairs);
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(2);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+        assertThat(listing.entries[1].key).isEqualTo(TEST_KEY_2);
+        assertThat(listing.entries[1].hash).isEqualTo(TEST_HASH_2.getHash());
+    }
+
+    @Test
+    public void emptyListing_returnsListingWithoutAnyPairs() {
+        KeyValueListingProto.KeyValueListing emptyListing = KeyValueListingBuilder.emptyListing();
+        assertThat(emptyListing.entries).isEmpty();
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java
new file mode 100644
index 0000000..ccfbfa4
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.backup.BackupDataInput;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.kv.KeyValueListingBuilder;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto.KeyValueListing;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto.KeyValuePair;
+import com.android.server.backup.encryption.tasks.BackupEncrypter.Result;
+import com.android.server.testing.shadows.DataEntity;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+@Config(shadows = {ShadowBackupDataInput.class})
+public class KvBackupEncrypterTest {
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+    private static final byte[] TEST_TERTIARY_KEY = Arrays.copyOf(new byte[0], 256 / Byte.SIZE);
+    private static final String TEST_KEY_1 = "test_key_1";
+    private static final String TEST_KEY_2 = "test_key_2";
+    private static final String TEST_KEY_3 = "test_key_3";
+    private static final byte[] TEST_VALUE_1 = {10, 11, 12};
+    private static final byte[] TEST_VALUE_2 = {13, 14, 15};
+    private static final byte[] TEST_VALUE_2B = {13, 14, 15, 16};
+    private static final byte[] TEST_VALUE_3 = {16, 17, 18};
+
+    private SecretKey mSecretKey;
+    private ChunkHasher mChunkHasher;
+
+    @Before
+    public void setUp() {
+        mSecretKey = new SecretKeySpec(TEST_TERTIARY_KEY, KEY_ALGORITHM);
+        mChunkHasher = new ChunkHasher(mSecretKey);
+
+        ShadowBackupDataInput.reset();
+    }
+
+    private KvBackupEncrypter createEncrypter(KeyValueListing keyValueListing) {
+        KvBackupEncrypter encrypter = new KvBackupEncrypter(new BackupDataInput(null));
+        encrypter.setOldKeyValueListing(keyValueListing);
+        return encrypter;
+    }
+
+    @Test
+    public void backup_noExistingBackup_encryptsAllPairs() throws Exception {
+        ShadowBackupDataInput.addEntity(TEST_KEY_1, TEST_VALUE_1);
+        ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2);
+
+        KeyValueListing emptyKeyValueListing = new KeyValueListingBuilder().build();
+        ImmutableSet<ChunkHash> emptyExistingChunks = ImmutableSet.of();
+        KvBackupEncrypter encrypter = createEncrypter(emptyKeyValueListing);
+
+        Result result =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, emptyExistingChunks);
+
+        assertThat(result.getAllChunks()).hasSize(2);
+        EncryptedChunk chunk1 = result.getNewChunks().get(0);
+        EncryptedChunk chunk2 = result.getNewChunks().get(1);
+        assertThat(chunk1.key()).isEqualTo(getChunkHash(TEST_KEY_1, TEST_VALUE_1));
+        KeyValuePair pair1 = decryptChunk(chunk1);
+        assertThat(pair1.key).isEqualTo(TEST_KEY_1);
+        assertThat(pair1.value).isEqualTo(TEST_VALUE_1);
+        assertThat(chunk2.key()).isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2));
+        KeyValuePair pair2 = decryptChunk(chunk2);
+        assertThat(pair2.key).isEqualTo(TEST_KEY_2);
+        assertThat(pair2.value).isEqualTo(TEST_VALUE_2);
+    }
+
+    @Test
+    public void backup_existingBackup_encryptsNewAndUpdatedPairs() throws Exception {
+        Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2();
+
+        // Update key 2 and add the new key 3.
+        ShadowBackupDataInput.reset();
+        ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2B);
+        ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3);
+
+        KvBackupEncrypter encrypter = createEncrypter(initialResult.first);
+        BackupEncrypter.Result secondResult =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second);
+
+        assertThat(secondResult.getAllChunks()).hasSize(3);
+        assertThat(secondResult.getNewChunks()).hasSize(2);
+        EncryptedChunk newChunk2 = secondResult.getNewChunks().get(0);
+        EncryptedChunk newChunk3 = secondResult.getNewChunks().get(1);
+        assertThat(newChunk2.key()).isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2B));
+        assertThat(decryptChunk(newChunk2).value).isEqualTo(TEST_VALUE_2B);
+        assertThat(newChunk3.key()).isEqualTo(getChunkHash(TEST_KEY_3, TEST_VALUE_3));
+        assertThat(decryptChunk(newChunk3).value).isEqualTo(TEST_VALUE_3);
+    }
+
+    @Test
+    public void backup_allChunksContainsHashesOfAllChunks() throws Exception {
+        Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2();
+
+        ShadowBackupDataInput.reset();
+        ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3);
+
+        KvBackupEncrypter encrypter = createEncrypter(initialResult.first);
+        BackupEncrypter.Result secondResult =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second);
+
+        assertThat(secondResult.getAllChunks())
+                .containsExactly(
+                        getChunkHash(TEST_KEY_1, TEST_VALUE_1),
+                        getChunkHash(TEST_KEY_2, TEST_VALUE_2),
+                        getChunkHash(TEST_KEY_3, TEST_VALUE_3));
+    }
+
+    @Test
+    public void backup_negativeSize_deletesKeyFromExistingBackup() throws Exception {
+        Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2();
+
+        ShadowBackupDataInput.reset();
+        ShadowBackupDataInput.addEntity(new DataEntity(TEST_KEY_2));
+
+        KvBackupEncrypter encrypter = createEncrypter(initialResult.first);
+        Result secondResult =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second);
+
+        assertThat(secondResult.getAllChunks())
+                .containsExactly(getChunkHash(TEST_KEY_1, TEST_VALUE_1));
+        assertThat(secondResult.getNewChunks()).isEmpty();
+    }
+
+    @Test
+    public void backup_returnsMessageDigestOverChunkHashes() throws Exception {
+        Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2();
+
+        ShadowBackupDataInput.reset();
+        ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3);
+
+        KvBackupEncrypter encrypter = createEncrypter(initialResult.first);
+        Result secondResult =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second);
+
+        MessageDigest messageDigest =
+                MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM);
+        ImmutableList<ChunkHash> sortedHashes =
+                Ordering.natural()
+                        .immutableSortedCopy(
+                                ImmutableList.of(
+                                        getChunkHash(TEST_KEY_1, TEST_VALUE_1),
+                                        getChunkHash(TEST_KEY_2, TEST_VALUE_2),
+                                        getChunkHash(TEST_KEY_3, TEST_VALUE_3)));
+        messageDigest.update(sortedHashes.get(0).getHash());
+        messageDigest.update(sortedHashes.get(1).getHash());
+        messageDigest.update(sortedHashes.get(2).getHash());
+        assertThat(secondResult.getDigest()).isEqualTo(messageDigest.digest());
+    }
+
+    @Test
+    public void getNewKeyValueListing_noExistingBackup_returnsCorrectListing() throws Exception {
+        KeyValueListing keyValueListing = runInitialBackupOfPairs1And2().first;
+
+        assertThat(keyValueListing.entries.length).isEqualTo(2);
+        assertThat(keyValueListing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(keyValueListing.entries[0].hash)
+                .isEqualTo(getChunkHash(TEST_KEY_1, TEST_VALUE_1).getHash());
+        assertThat(keyValueListing.entries[1].key).isEqualTo(TEST_KEY_2);
+        assertThat(keyValueListing.entries[1].hash)
+                .isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2).getHash());
+    }
+
+    @Test
+    public void getNewKeyValueListing_existingBackup_returnsCorrectListing() throws Exception {
+        Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2();
+
+        ShadowBackupDataInput.reset();
+        ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2B);
+        ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3);
+
+        KvBackupEncrypter encrypter = createEncrypter(initialResult.first);
+        encrypter.backup(mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second);
+
+        ImmutableMap<String, ChunkHash> keyValueListing =
+                listingToMap(encrypter.getNewKeyValueListing());
+        assertThat(keyValueListing).hasSize(3);
+        assertThat(keyValueListing)
+                .containsEntry(TEST_KEY_1, getChunkHash(TEST_KEY_1, TEST_VALUE_1));
+        assertThat(keyValueListing)
+                .containsEntry(TEST_KEY_2, getChunkHash(TEST_KEY_2, TEST_VALUE_2B));
+        assertThat(keyValueListing)
+                .containsEntry(TEST_KEY_3, getChunkHash(TEST_KEY_3, TEST_VALUE_3));
+    }
+
+    @Test
+    public void getNewKeyValueChunkListing_beforeBackup_throws() throws Exception {
+        KvBackupEncrypter encrypter = createEncrypter(new KeyValueListing());
+        assertThrows(IllegalStateException.class, encrypter::getNewKeyValueListing);
+    }
+
+    private ImmutableMap<String, ChunkHash> listingToMap(KeyValueListing listing) {
+        // We can't use the ImmutableMap collector directly because it isn't supported in Android
+        // guava.
+        return ImmutableMap.copyOf(
+                Arrays.stream(listing.entries)
+                        .collect(
+                                Collectors.toMap(
+                                        entry -> entry.key, entry -> new ChunkHash(entry.hash))));
+    }
+
+    private Pair<KeyValueListing, Set<ChunkHash>> runInitialBackupOfPairs1And2() throws Exception {
+        ShadowBackupDataInput.addEntity(TEST_KEY_1, TEST_VALUE_1);
+        ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2);
+
+        KeyValueListing initialKeyValueListing = new KeyValueListingBuilder().build();
+        ImmutableSet<ChunkHash> initialExistingChunks = ImmutableSet.of();
+        KvBackupEncrypter encrypter = createEncrypter(initialKeyValueListing);
+        Result firstResult =
+                encrypter.backup(
+                        mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialExistingChunks);
+
+        return Pair.create(
+                encrypter.getNewKeyValueListing(), ImmutableSet.copyOf(firstResult.getAllChunks()));
+    }
+
+    private ChunkHash getChunkHash(String key, byte[] value) throws Exception {
+        KeyValuePair pair = new KeyValuePair();
+        pair.key = key;
+        pair.value = Arrays.copyOf(value, value.length);
+        return mChunkHasher.computeHash(KeyValuePair.toByteArray(pair));
+    }
+
+    private KeyValuePair decryptChunk(EncryptedChunk encryptedChunk) throws Exception {
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(
+                Cipher.DECRYPT_MODE,
+                mSecretKey,
+                new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * Byte.SIZE, encryptedChunk.nonce()));
+        byte[] decryptedBytes = cipher.doFinal(encryptedChunk.encryptedBytes());
+        return KeyValuePair.parseFrom(decryptedBytes);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java
new file mode 100644
index 0000000..4ac4fa8
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.security.SecureRandom;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowRecoveryController.class})
+@Presubmit
+public class StartSecondaryKeyRotationTaskTest {
+
+    private CryptoSettings mCryptoSettings;
+    private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask;
+
+    @Before
+    public void setUp() throws Exception {
+        mSecondaryKeyManager =
+                new RecoverableKeyStoreSecondaryKeyManager(
+                        RecoveryController.getInstance(RuntimeEnvironment.application),
+                        new SecureRandom());
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application);
+        mStartSecondaryKeyRotationTask =
+                new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager);
+
+        ShadowRecoveryController.reset();
+    }
+
+    @Test
+    public void run_doesNothingIfNoActiveSecondaryExists() {
+        mStartSecondaryKeyRotationTask.run();
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception {
+        generateAnActiveKey();
+        String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get();
+        mCryptoSettings.setNextSecondaryAlias(activeAlias);
+
+        mStartSecondaryKeyRotationTask.run();
+
+        assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue();
+    }
+
+    @Test
+    public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception {
+        generateAnActiveKey();
+        RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate();
+        String nextAlias = nextKey.getAlias();
+        mCryptoSettings.setNextSecondaryAlias(nextAlias);
+
+        mStartSecondaryKeyRotationTask.run();
+
+        assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse();
+    }
+
+    @Test
+    public void run_generatesANewNextSecondaryKey() throws Exception {
+        generateAnActiveKey();
+
+        mStartSecondaryKeyRotationTask.run();
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue();
+    }
+
+    @Test
+    public void run_generatesANewKeyThatExistsInKeyStore() throws Exception {
+        generateAnActiveKey();
+
+        mStartSecondaryKeyRotationTask.run();
+
+        String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get();
+        assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue();
+    }
+
+    private void generateAnActiveKey() throws Exception {
+        RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate();
+        mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias());
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java
new file mode 100644
index 0000000..faddb6c
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.testing;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+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.RandomAccessFile;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+/**
+ * To be used as part of a fake backup server. Processes a Scotty diff script.
+ *
+ * <p>A Scotty diff script consists of an ASCII line denoting a command, optionally followed by a
+ * range of bytes. Command format is either
+ *
+ * <ul>
+ *   <li>A single 64-bit integer, followed by a new line: this denotes that the given number of
+ *       bytes are to follow in the stream. These bytes should be written directly to the new file.
+ *   <li>Two 64-bit integers, separated by a hyphen, followed by a new line: this says that the
+ *       given range of bytes from the original file ought to be copied into the new file.
+ * </ul>
+ */
+public class DiffScriptProcessor {
+
+    private static final int COPY_BUFFER_SIZE = 1024;
+
+    private static final String READ_MODE = "r";
+    private static final Pattern VALID_COMMAND_PATTERN = Pattern.compile("^\\d+(-\\d+)?$");
+
+    private final File mInput;
+    private final File mOutput;
+    private final long mInputLength;
+
+    /**
+     * A new instance, with {@code input} as previous file, and {@code output} as new file.
+     *
+     * @param input Previous file from which ranges of bytes are to be copied. This file should be
+     *     immutable.
+     * @param output Output file, to which the new data should be written.
+     * @throws IllegalArgumentException if input does not exist.
+     */
+    public DiffScriptProcessor(File input, File output) {
+        checkArgument(input.exists(), "input file did not exist.");
+        mInput = input;
+        mInputLength = input.length();
+        mOutput = checkNotNull(output);
+    }
+
+    public void process(InputStream diffScript) throws IOException, MalformedDiffScriptException {
+        RandomAccessFile randomAccessInput = new RandomAccessFile(mInput, READ_MODE);
+
+        try (FileOutputStream outputStream = new FileOutputStream(mOutput)) {
+            while (true) {
+                Optional<String> commandString = readCommand(diffScript);
+                if (!commandString.isPresent()) {
+                    return;
+                }
+                Command command = Command.parse(commandString.get());
+
+                if (command.mIsRange) {
+                    checkFileRange(command.mCount, command.mLimit);
+                    copyRange(randomAccessInput, outputStream, command.mCount, command.mLimit);
+                } else {
+                    long bytesCopied = copyBytes(diffScript, outputStream, command.mCount);
+                    if (bytesCopied < command.mCount) {
+                        throw new MalformedDiffScriptException(
+                                String.format(
+                                        Locale.US,
+                                        "Command to copy %d bytes from diff script, but only %d"
+                                            + " bytes available",
+                                        command.mCount,
+                                        bytesCopied));
+                    }
+                    if (diffScript.read() != '\n') {
+                        throw new MalformedDiffScriptException("Expected new line after bytes.");
+                    }
+                }
+            }
+        }
+    }
+
+    private void checkFileRange(long start, long end) throws MalformedDiffScriptException {
+        if (end < start) {
+            throw new MalformedDiffScriptException(
+                    String.format(
+                            Locale.US,
+                            "Command to copy %d-%d bytes from original file, but %2$d < %1$d.",
+                            start,
+                            end));
+        }
+
+        if (end >= mInputLength) {
+            throw new MalformedDiffScriptException(
+                    String.format(
+                            Locale.US,
+                            "Command to copy %d-%d bytes from original file, but file is only %d"
+                                + " bytes long.",
+                            start,
+                            end,
+                            mInputLength));
+        }
+    }
+
+    /**
+     * Reads a command from the input stream.
+     *
+     * @param inputStream The input.
+     * @return Optional of command, or empty if EOF.
+     */
+    private static Optional<String> readCommand(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+        int b;
+        while (!isEndOfCommand(b = inputStream.read())) {
+            byteArrayOutputStream.write(b);
+        }
+
+        byte[] bytes = byteArrayOutputStream.toByteArray();
+        if (bytes.length == 0) {
+            return Optional.empty();
+        } else {
+            return Optional.of(new String(bytes, UTF_8));
+        }
+    }
+
+    /**
+     * If the given output from {@link InputStream#read()} is the end of a command - i.e., a new
+     * line or the EOF.
+     *
+     * @param b The byte or -1.
+     * @return {@code true} if ends the command.
+     */
+    private static boolean isEndOfCommand(int b) {
+        return b == -1 || b == '\n';
+    }
+
+    /**
+     * Copies {@code n} bytes from {@code inputStream} to {@code outputStream}.
+     *
+     * @return The number of bytes copied.
+     * @throws IOException if there was a problem reading or writing.
+     */
+    private static long copyBytes(InputStream inputStream, OutputStream outputStream, long n)
+            throws IOException {
+        byte[] buffer = new byte[COPY_BUFFER_SIZE];
+        long copied = 0;
+        while (n - copied > COPY_BUFFER_SIZE) {
+            long read = copyBlock(inputStream, outputStream, buffer, COPY_BUFFER_SIZE);
+            if (read <= 0) {
+                return copied;
+            }
+        }
+        while (n - copied > 0) {
+            copied += copyBlock(inputStream, outputStream, buffer, (int) (n - copied));
+        }
+        return copied;
+    }
+
+    private static long copyBlock(
+            InputStream inputStream, OutputStream outputStream, byte[] buffer, int size)
+            throws IOException {
+        int read = inputStream.read(buffer, 0, size);
+        outputStream.write(buffer, 0, read);
+        return read;
+    }
+
+    /**
+     * Copies the given range of bytes from the input file to the output stream.
+     *
+     * @param input The input file.
+     * @param output The output stream.
+     * @param start Start position in the input file.
+     * @param end End position in the output file (inclusive).
+     * @throws IOException if there was a problem reading or writing.
+     */
+    private static void copyRange(RandomAccessFile input, OutputStream output, long start, long end)
+            throws IOException {
+        input.seek(start);
+
+        // Inefficient but obviously correct. If tests become slow, optimize.
+        for (; start <= end; start++) {
+            output.write(input.read());
+        }
+    }
+
+    /** Error thrown for a malformed diff script. */
+    public static class MalformedDiffScriptException extends Exception {
+        public MalformedDiffScriptException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * A command telling the processor either to insert n bytes, which follow, or copy n-m bytes
+     * from the original file.
+     */
+    private static class Command {
+        private final long mCount;
+        private final long mLimit;
+        private final boolean mIsRange;
+
+        private Command(long count, long limit, boolean isRange) {
+            mCount = count;
+            mLimit = limit;
+            mIsRange = isRange;
+        }
+
+        /**
+         * Attempts to parse the command string into a usable structure.
+         *
+         * @param command The command string, without a new line at the end.
+         * @throws MalformedDiffScriptException if the command is not a valid diff script command.
+         * @return The parsed command.
+         */
+        private static Command parse(String command) throws MalformedDiffScriptException {
+            if (!VALID_COMMAND_PATTERN.matcher(command).matches()) {
+                throw new MalformedDiffScriptException("Bad command: " + command);
+            }
+
+            Scanner commandScanner = new Scanner(command);
+            commandScanner.useDelimiter("-");
+            long n = commandScanner.nextLong();
+            if (!commandScanner.hasNextLong()) {
+                return new Command(n, 0L, /*isRange=*/ false);
+            }
+            long m = commandScanner.nextLong();
+            return new Command(n, m, /*isRange=*/ true);
+        }
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
new file mode 100644
index 0000000..196d47e
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.testing;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+/** Helpers for crypto code tests. */
+public class CryptoTestUtils {
+    private static final String KEY_ALGORITHM = "AES";
+    private static final int KEY_SIZE_BITS = 256;
+
+    private CryptoTestUtils() {}
+
+    public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(KEY_SIZE_BITS);
+        return keyGenerator.generateKey();
+    }
+
+    /** Generates a byte array of size {@code n} containing random bytes. */
+    public static byte[] generateRandomBytes(int n) {
+        byte[] bytes = new byte[n];
+        Random random = new Random();
+        random.nextBytes(bytes);
+        return bytes;
+    }
+
+    public static ChunksMetadataProto.Chunk newChunk(ChunkHash hash, int length) {
+        return newChunk(hash.getHash(), length);
+    }
+
+    public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) {
+        ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk();
+        newChunk.hash = Arrays.copyOf(hash, hash.length);
+        newChunk.length = length;
+        return newChunk;
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java
new file mode 100644
index 0000000..6d3b5e9
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing.shadows;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Represents a key value pair in {@link ShadowBackupDataInput} and {@link ShadowBackupDataOutput}.
+ */
+public class DataEntity {
+    public final String mKey;
+    public final byte[] mValue;
+    public final int mSize;
+
+    /**
+     * Constructs a pair with a string value. The value will be converted to a byte array in {@link
+     * StandardCharsets#UTF_8}.
+     */
+    public DataEntity(String key, String value) {
+        this.mKey = checkNotNull(key);
+        this.mValue = value.getBytes(StandardCharsets.UTF_8);
+        mSize = this.mValue.length;
+    }
+
+    /**
+     * Constructs a new entity with the given key but a negative size. This represents a deleted
+     * pair.
+     */
+    public DataEntity(String key) {
+        this.mKey = checkNotNull(key);
+        mSize = -1;
+        mValue = null;
+    }
+
+    /** Constructs a new entity where the size of the value is the entire array. */
+    public DataEntity(String key, byte[] value) {
+        this(key, value, value.length);
+    }
+
+    /**
+     * Constructs a new entity.
+     *
+     * @param key the key of the pair
+     * @param data the value to associate with the key
+     * @param size the length of the value in bytes
+     */
+    public DataEntity(String key, byte[] data, int size) {
+        this.mKey = checkNotNull(key);
+        this.mSize = size;
+        mValue = new byte[size];
+        for (int i = 0; i < size; i++) {
+            mValue[i] = data[i];
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DataEntity that = (DataEntity) o;
+
+        if (mSize != that.mSize) {
+            return false;
+        }
+        if (!mKey.equals(that.mKey)) {
+            return false;
+        }
+        return Arrays.equals(mValue, that.mValue);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mKey.hashCode();
+        result = 31 * result + Arrays.hashCode(mValue);
+        result = 31 * result + mSize;
+        return result;
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
new file mode 100644
index 0000000..7ac6ec4
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing.shadows;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import android.annotation.Nullable;
+import android.app.backup.BackupDataInput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Shadow for BackupDataInput. */
+@Implements(BackupDataInput.class)
+public class ShadowBackupDataInput {
+    private static final List<DataEntity> ENTITIES = new ArrayList<>();
+    @Nullable private static IOException sReadNextHeaderException;
+
+    @Nullable private ByteArrayInputStream mCurrentEntityInputStream;
+    private int mCurrentEntity = -1;
+
+    /** Resets the shadow, clearing any entities or exception. */
+    public static void reset() {
+        ENTITIES.clear();
+        sReadNextHeaderException = null;
+    }
+
+    /** Sets the exception which the input will throw for any call to {@link #readNextHeader}. */
+    public static void setReadNextHeaderException(@Nullable IOException readNextHeaderException) {
+        ShadowBackupDataInput.sReadNextHeaderException = readNextHeaderException;
+    }
+
+    /** Adds the given entity to the input. */
+    public static void addEntity(DataEntity e) {
+        ENTITIES.add(e);
+    }
+
+    /** Adds an entity to the input with the given key and value. */
+    public static void addEntity(String key, byte[] value) {
+        ENTITIES.add(new DataEntity(key, value, value.length));
+    }
+
+    public void __constructor__(FileDescriptor fd) {}
+
+    @Implementation
+    public boolean readNextHeader() throws IOException {
+        if (sReadNextHeaderException != null) {
+            throw sReadNextHeaderException;
+        }
+
+        mCurrentEntity++;
+
+        if (mCurrentEntity >= ENTITIES.size()) {
+            return false;
+        }
+
+        byte[] value = ENTITIES.get(mCurrentEntity).mValue;
+        if (value == null) {
+            mCurrentEntityInputStream = new ByteArrayInputStream(new byte[0]);
+        } else {
+            mCurrentEntityInputStream = new ByteArrayInputStream(value);
+        }
+        return true;
+    }
+
+    @Implementation
+    public String getKey() {
+        return ENTITIES.get(mCurrentEntity).mKey;
+    }
+
+    @Implementation
+    public int getDataSize() {
+        return ENTITIES.get(mCurrentEntity).mSize;
+    }
+
+    @Implementation
+    public void skipEntityData() {
+        // Do nothing.
+    }
+
+    @Implementation
+    public int readEntityData(byte[] data, int offset, int size) {
+        checkState(mCurrentEntityInputStream != null, "Must call readNextHeader() first");
+        return mCurrentEntityInputStream.read(data, offset, size);
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
similarity index 100%
rename from services/robotests/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowRecoveryController.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java
similarity index 100%
rename from services/robotests/src/com/android/server/testing/shadows/ShadowRecoveryController.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java
diff --git a/packages/BackupRestoreConfirmation/res/values-in/strings.xml b/packages/BackupRestoreConfirmation/res/values-in/strings.xml
index 63a3772..5c82adf 100644
--- a/packages/BackupRestoreConfirmation/res/values-in/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-in/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="backup_confirm_title" msgid="827563724209303345">"Backup sepenuhnya"</string>
+    <string name="backup_confirm_title" msgid="827563724209303345">"Pencadangan lengkap"</string>
     <string name="restore_confirm_title" msgid="5469365809567486602">"Pemulihan sepenuhnya"</string>
-    <string name="backup_confirm_text" msgid="1878021282758896593">"Backup lengkap semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?\n\nJika Anda tidak meminta backup ini, jangan izinkan operasi dilanjutkan."</string>
+    <string name="backup_confirm_text" msgid="1878021282758896593">"Pencadangan semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?\n\nJika Anda tidak meminta pencadangan ini, jangan izinkan operasi dilanjutkan."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"Cadangkan data saya"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"Jangan mencadangkan"</string>
     <string name="restore_confirm_text" msgid="7499866728030461776">"Pemulihan lengkap semua data dari komputer desktop yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini?\n\nJika Anda tidak meminta pemulihan ini, jangan izinkan operasi dilanjutkan. Operasi ini akan mengganti data apa pun yang saat ini ada dalam perangkat!"</string>
@@ -31,8 +31,8 @@
     <string name="backup_enc_password_optional" msgid="1350137345907579306">"Jika Anda ingin mengenkripsi data cadangan lengkap, masukkan sandi di bawah:"</string>
     <string name="backup_enc_password_required" msgid="7889652203371654149">"Karena perangkat Anda dienkripsi, Anda perlu mengenkripsi cadangan. Masukkan sandi di bawah:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"Jika data pemulihan dienkripsi, masukkan sandi di bawah:"</string>
-    <string name="toast_backup_started" msgid="550354281452756121">"Backup dimulai..."</string>
-    <string name="toast_backup_ended" msgid="3818080769548726424">"Backup selesai"</string>
+    <string name="toast_backup_started" msgid="550354281452756121">"Pencadangan dimulai..."</string>
+    <string name="toast_backup_ended" msgid="3818080769548726424">"Pencadangan selesai"</string>
     <string name="toast_restore_started" msgid="7881679218971277385">"Pemulihan dimulai..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"Pemulihan berakhir"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"Waktu tunggu operasi habis"</string>
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index bbae9ff..672879a 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -13,18 +13,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-android_app {
-    name: "CarSystemUI",
-
-    overrides: [
-        "SystemUI",
-    ],
+android_library {
+    name: "CarSystemUI-core",
 
     srcs: [
         "src/**/*.java",
         "src/**/I*.aidl",
     ],
 
+    resource_dirs: [
+        "res-keyguard",
+        "res",
+    ],
+
     static_libs: [
         "SystemUI-core",
         "CarNotificationLib",
@@ -58,6 +59,28 @@
 
     manifest: "AndroidManifest.xml",
 
+    plugins: ["dagger2-compiler-2.19"],
+
+}
+
+android_app {
+    name: "CarSystemUI",
+
+    static_libs: [
+        "CarSystemUI-core",
+    ],
+
+    libs: [
+        "telephony-common",
+        "android.car",
+    ],
+
+    resource_dirs: [],
+
+     overrides: [
+        "SystemUI",
+    ],
+
     platform_apis: true,
     product_specific: true,
     certificate: "platform",
@@ -68,12 +91,6 @@
             "proguard.flags",
         ],
     },
-    resource_dirs: [
-        "res-keyguard",
-        "res",
-    ],
-
-
     dxflags: ["--multi-dex"],
 
     aaptflags: [
@@ -81,6 +98,8 @@
         "com.android.keyguard",
     ],
 
+    kotlincflags: ["-Xjvm-default=enable"],
+
     plugins: ["dagger2-compiler-2.19"],
 
     required: ["privapp_whitelist_com.android.systemui"],
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 728a239..e1bcc2e 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -42,6 +42,14 @@
     </com.android.systemui.statusbar.BackDropView>
 
     <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_for_bubble"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <com.android.systemui.statusbar.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml b/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
index 2d9901c..9df78f1 100644
--- a/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
+++ b/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
@@ -16,12 +16,13 @@
   -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layout"
+    android:id="@+id/unlock_dialog_parent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center">
 
     <LinearLayout
+        android:id="@+id/unlock_dialog"
         android:layout_width="@dimen/unlock_dialog_width"
         android:layout_height="wrap_content"
         android:gravity="center"
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index e0ae4566..5fcf38fc 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -32,7 +32,7 @@
     <color name="system_bar_background_opaque">#ff172026</color>
 
     <color name="status_bar_background_color">#33000000</color>
-    <drawable name="system_bar_background">@android:color/transparent</drawable>
+    <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
 
     <!-- The background color of the notification shade -->
     <color name="notification_shade_background_color">#D6000000</color>
diff --git a/packages/CarSystemUI/res/values/integers_car.xml b/packages/CarSystemUI/res/values/integers_car.xml
index 862ba75..fb67b30 100644
--- a/packages/CarSystemUI/res/values/integers_car.xml
+++ b/packages/CarSystemUI/res/values/integers_car.xml
@@ -32,6 +32,6 @@
     at will result in remaining closed the panel if released-->
     <integer name="notification_settle_close_percentage">80</integer>
     <!-- The delay before the unlock dialog pops up -->
-    <integer name="unlock_dialog_delay_ms">3000</integer>
+    <integer name="unlock_dialog_delay_ms">0</integer>
 
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index d6b766b..264b7d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -25,7 +25,6 @@
         modules = {
                 DependencyProvider.class,
                 DependencyBinder.class,
-                ServiceBinder.class,
                 SystemUIFactory.ContextHolder.class,
                 SystemUIModule.class,
                 CarSystemUIModule.class
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index ec40cc3..afd722b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -30,8 +32,10 @@
         NotificationInterruptionStateProvider {
 
     @Inject
-    public CarNotificationInterruptionStateProvider(Context context) {
-        super(context);
+    public CarNotificationInterruptionStateProvider(Context context,
+            NotificationFilter filter,
+            StatusBarStateController stateController) {
+        super(context, filter, stateController);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7ea83f5..b0ab5b4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -948,8 +948,8 @@
         pw.print("  mNavigationBarView=");
         pw.println(mNavigationBarView);
 
-        if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
-            KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
+        if (Dependency.get(KeyguardUpdateMonitor.class) != null) {
+            Dependency.get(KeyguardUpdateMonitor.class).dump(fd, pw, args);
         }
 
         Dependency.get(FalsingManager.class).dump(pw);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
index 78bb1bc..ec72ee7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
@@ -52,7 +52,7 @@
      * Not using Dialog because context passed from {@link FullscreenUserSwitcher} is not an
      * activity.
      */
-    private final View mUnlockDialog;
+    private final View mUnlockDialogLayout;
     private final TextView mUnlockingText;
     private final Button mButton;
     private final IntentFilter mFilter;
@@ -70,14 +70,25 @@
         mParams.packageName = mContext.getPackageName();
         mParams.setTitle(mContext.getString(R.string.unlock_dialog_title));
 
-        mUnlockDialog = LayoutInflater.from(mContext).inflate(
+        mUnlockDialogLayout = LayoutInflater.from(mContext).inflate(
             R.layout.trust_agent_unlock_dialog, null);
-        mUnlockDialog.setLayoutParams(mParams);
+        mUnlockDialogLayout.setLayoutParams(mParams);
 
-        mUnlockingText = mUnlockDialog.findViewById(R.id.unlocking_text);
-        mButton = mUnlockDialog.findViewById(R.id.enter_pin_button);
+        View dialogParent = mUnlockDialogLayout.findViewById(R.id.unlock_dialog_parent);
+        dialogParent.setOnTouchListener((v, event)-> {
+            hideUnlockDialog(/* dismissUserSwitcher= */ false);
+            return true;
+        });
+        View unlockDialog = mUnlockDialogLayout.findViewById(R.id.unlock_dialog);
+        unlockDialog.setOnTouchListener((v, event) -> {
+            // If the person taps inside the unlock dialog, the touch event will be intercepted here
+            // and the dialog will not exit
+            return true;
+        });
+        mUnlockingText = mUnlockDialogLayout.findViewById(R.id.unlocking_text);
+        mButton = mUnlockDialogLayout.findViewById(R.id.enter_pin_button);
         mButton.setOnClickListener(v -> {
-            hideUnlockDialog(/* notifyOnHideListener= */true);
+            hideUnlockDialog(/* dismissUserSwitcher= */true);
             // TODO(b/138250105) Stop unlock advertising
         });
 
@@ -133,7 +144,7 @@
                 if (!mUserManager.isUserUnlocked(uid)) {
                     logd("Showed unlock dialog for user: " + uid + " after " + delayMillis
                             + " delay.");
-                    mWindowManager.addView(mUnlockDialog, mParams);
+                    mWindowManager.addView(mUnlockDialogLayout, mParams);
                 }
             }, delayMillis);
         }
@@ -142,22 +153,22 @@
 
     private void setUid(int uid) {
         mUid = uid;
-        TextView userName = mUnlockDialog.findViewById(R.id.user_name);
+        TextView userName = mUnlockDialogLayout.findViewById(R.id.user_name);
         userName.setText(mUserManager.getUserInfo(mUid).name);
-        ImageView avatar = mUnlockDialog.findViewById(R.id.avatar);
+        ImageView avatar = mUnlockDialogLayout.findViewById(R.id.avatar);
         avatar.setImageBitmap(mUserManager.getUserIcon(mUid));
         setButtonText();
     }
 
-    private void hideUnlockDialog(boolean notifyOnHideListener) {
+    private void hideUnlockDialog(boolean dismissUserSwitcher) {
         if (!mIsDialogShowing) {
             return;
         }
-        mWindowManager.removeView(mUnlockDialog);
+        mWindowManager.removeView(mUnlockDialogLayout);
         logd("Receiver unregistered");
         mContext.unregisterReceiver(this);
-        if (notifyOnHideListener && mOnHideListener != null) {
-            mOnHideListener.onHide();
+        if (mOnHideListener != null) {
+            mOnHideListener.onHide(dismissUserSwitcher);
         }
         mIsDialogShowing = false;
     }
@@ -192,23 +203,28 @@
         }
     }
 
-    // Set button text based on security lock type
+    // Set button text based on screen lock type
     private void setButtonText() {
         LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid);
         switch (passwordQuality) {
             // PIN
+            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+                mButton.setText(R.string.unlock_dialog_button_text_pin);
+                break;
             // Pattern
             case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
                 mButton.setText(R.string.unlock_dialog_button_text_pattern);
                 break;
             // Password
-            case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
                 mButton.setText(R.string.unlock_dialog_button_text_password);
                 break;
             default:
-                Log.e(TAG, "Encountered unexpected security type when attempting to set "
+                Log.e(TAG, "Encountered unexpected screen lock type when attempting to set "
                         + "button text:" + passwordQuality);
         }
     }
@@ -235,6 +251,6 @@
      * Listener used to notify when the dialog is hidden
      */
     interface OnHideListener {
-        void onHide();
+        void onHide(boolean dismissUserSwitcher);
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 7cd6adb..0f7c1ee 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -34,6 +34,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
 import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
 
 /**
@@ -116,7 +117,7 @@
         // Show unlock dialog for initial user
         if (hasTrustedDevice(initialUser)) {
             mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
-                    () -> dismissUserSwitcher());
+                    mOnHideListener);
         }
     }
 
@@ -162,7 +163,7 @@
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
         mSelectedUser = record;
         if (hasTrustedDevice(record.mInfo.id)) {
-            mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, () -> dismissUserSwitcher());
+            mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
             return;
         }
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -202,4 +203,18 @@
     private boolean hasTrustedDevice(int uid) {
         return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
     }
+
+    private OnHideListener mOnHideListener = new OnHideListener() {
+        @Override
+        public void onHide(boolean dismissUserSwitcher) {
+            if (dismissUserSwitcher) {
+                dismissUserSwitcher();
+            } else {
+                // Re-draw the parent view, otherwise the unlock dialog will not be removed from
+                // the screen immediately.
+                mParent.invalidate();
+            }
+
+        }
+    };
 }
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 96144cf..5655abb 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -17,7 +17,6 @@
     certificate: "platform",
     libs: [
         "android.test.runner",
-        "telephony-common",
         "android.test.base",
     ],
     static_libs: [
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index b0e2700..1c0e409 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -62,7 +62,7 @@
                 android:exported="false" />
 
         <activity android:name=".DeleteStagedFileOnResult"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
+            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 55ff591..a2fa461 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -135,6 +135,7 @@
 
         mEnableOk = true;
         mOk.setEnabled(true);
+        mOk.setFilterTouchesWhenObscured(true);
     }
 
     /**
diff --git a/packages/SettingsLib/HelpUtils/AndroidManifest.xml b/packages/SettingsLib/HelpUtils/AndroidManifest.xml
index 5240ce4..ccad6e4 100644
--- a/packages/SettingsLib/HelpUtils/AndroidManifest.xml
+++ b/packages/SettingsLib/HelpUtils/AndroidManifest.xml
@@ -16,6 +16,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.helputils">
+    package="com.android.settingslib.widget">
 
 </manifest>
diff --git a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
index e407d72..2d13b73 100644
--- a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
@@ -33,11 +33,11 @@
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 
+import com.android.settingslib.widget.R;
+
 import java.net.URISyntaxException;
 import java.util.Locale;
 
-import com.android.settingslib.helputils.R;
-
 /**
  * Functions to easily prepare contextual help menu option items with an intent that opens up the
  * browser to a particular URL, while taking into account the preferred language and app version.
diff --git a/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml b/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
index d19a022..0975640 100644
--- a/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
@@ -16,6 +16,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.restrictedlockutils">
+          package="com.android.settingslib.widget">
 
 </manifest>
\ No newline at end of file
diff --git a/packages/SettingsLib/SearchWidget/res/values-km/strings.xml b/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
index f012e3a..7ac9cb1 100644
--- a/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="search_menu" msgid="1604061903696928905">"ការកំណត់ការ​ស្វែងរក"</string>
+    <string name="search_menu" msgid="1604061903696928905">"​ស្វែងរកការកំណត់"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index afdb105..2649544 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en fas (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en fas"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en facis (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en facis"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Allarga la durada de la bateria després de les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Temps restant inferior a <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index a74b4ae..16d7ea4 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -169,7 +169,7 @@
     <string name="tts_engine_security_warning" msgid="8786238102020223650">"Այս խոսքային սինթեզի գործիքը կարող է հավաքել այն ամենը, ինչ արտասանված է, այդ թվում` անձնական տվյալներ, ինչպիսիք են գաղտնաբառն ու բանկային քարտի համարները: Սկզբնաբյուրը <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> շարժիչն է: Միացնե՞լ խոսքի սինթեզի շարժիչի օգտագործումը:"</string>
     <string name="tts_engine_network_required" msgid="1190837151485314743">"Այս լեզուն պահանջում է աշխատող ցանցային կապ գրվածքից խոսք ելքի համար:"</string>
     <string name="tts_default_sample_string" msgid="4040835213373086322">"Սա խոսքային սինթեզի մի նմուշ է:"</string>
-    <string name="tts_status_title" msgid="7268566550242584413">"Լռելյայն լեզվի կարգավիճակը"</string>
+    <string name="tts_status_title" msgid="7268566550242584413">"Կանխադրված լեզվի կարգավիճակը"</string>
     <string name="tts_status_ok" msgid="1309762510278029765">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը լիովին աջակցվում է"</string>
     <string name="tts_status_requires_network" msgid="6042500821503226892">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը պահանջում է ցանցային կապ"</string>
     <string name="tts_status_not_supported" msgid="4491154212762472495">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը չի աջակցվում"</string>
@@ -201,9 +201,9 @@
     <string name="vpn_settings_not_available" msgid="956841430176985598">"VPN-ի կարգավորումները հասանելի չեն այս օգտատիրոջը"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"Այս օգտատերը չի կարող փոխել մոդեմի ռեժիմի կարգավորումները"</string>
     <string name="apn_settings_not_available" msgid="7873729032165324000">"Մուտքի կետի անվան կարգավորումները հասանելի չեն այս օգտատիրոջը"</string>
-    <string name="enable_adb" msgid="7982306934419797485">"USB վրիպազերծում"</string>
+    <string name="enable_adb" msgid="7982306934419797485">"USB-ով վրիպազերծում"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"Միացնել վրիպազերծման ռեժիմը, երբ USB-ն միացված է"</string>
-    <string name="clear_adb_keys" msgid="4038889221503122743">"Չեղարկել USB վրիպազերծման լիազորումները"</string>
+    <string name="clear_adb_keys" msgid="4038889221503122743">"Չեղարկել USB-ով վրիպազերծման թույլտվությունները"</string>
     <string name="bugreport_in_power" msgid="7923901846375587241">"Սխալի հաղորդման դյուրանցում"</string>
     <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Գործարկման ցանկում ցույց տալ կոճակը՝ վրիպակների հաղորդման համար"</string>
     <string name="keep_screen_on" msgid="1146389631208760344">"Մնալ արթուն"</string>
@@ -263,9 +263,9 @@
     <string name="debug_view_attributes" msgid="6485448367803310384">"Միացնել ցուցադրման հատկանիշների ստուգումը"</string>
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Միշտ ակտիվացրած պահել բջջային տվյալները, նույնիսկ Wi‑Fi-ը միացրած ժամանակ (ցանցերի միջև արագ փոխարկման համար):"</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Օգտագործել սարքակազմի արագացման միացումը, եթե հասանելի է"</string>
-    <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB վրիպազերծումը:"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string>
-    <string name="adb_keys_warning_message" msgid="5659849457135841625">"Փակե՞լ USB-ի վրիպազերծման մուտքը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string>
+    <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB-ով վրիպազերծումը:"</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB-ով վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string>
+    <string name="adb_keys_warning_message" msgid="5659849457135841625">"Չեղարկե՞լ USB-ով վրիպազերծման հասանելիությունը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"Ընդունե՞լ ծրագրավորման կարգավորումներ:"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"Այս կարգավորումները միայն ծրագրավորման նպատակների համար են նախատեսված: Դրանք կարող են խանգարել ձեր սարքի կամ ծրագրի աշխատանքին:"</string>
     <string name="verify_apps_over_usb_title" msgid="4177086489869041953">"Ստուգել հավելվածները USB-ի նկատմամբ"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 7fe1ef9..02c4c8c 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -336,8 +336,8 @@
     <string name="force_resizable_activities_summary" msgid="6667493494706124459">"Buat semua aktivitas dapat diubah ukurannya untuk banyak jendela, terlepas dari nilai manifes."</string>
     <string name="enable_freeform_support" msgid="1461893351278940416">"Aktifkan jendela berformat bebas"</string>
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"Aktifkan dukungan untuk jendela eksperimental berformat bebas."</string>
-    <string name="local_backup_password_title" msgid="3860471654439418822">"Sandi backup desktop"</string>
-    <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Saat ini backup desktop sepenuhnya tidak dilindungi"</string>
+    <string name="local_backup_password_title" msgid="3860471654439418822">"Sandi cadangan desktop"</string>
+    <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Saat ini cadangan desktop penuh tidak dilindungi"</string>
     <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Ketuk guna mengubah atau menghapus sandi untuk cadangan lengkap desktop"</string>
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"Sandi cadangan baru telah disetel"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"Sandi baru dan konfirmasinya tidak cocok."</string>
@@ -387,8 +387,8 @@
     <skip />
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan Anda (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan Anda"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Hingga <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Perpanjang masa pakai baterai hingga <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Tersisa kurang dari <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 68c0f17..a43e8d6 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -240,7 +240,7 @@
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS privato"</string>
     <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Seleziona modalità DNS privato"</string>
-    <string name="private_dns_mode_off" msgid="8236575187318721684">"Non attivo"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"Off"</string>
     <string name="private_dns_mode_opportunistic" msgid="8314986739896927399">"Automatico"</string>
     <string name="private_dns_mode_provider" msgid="8354935160639360804">"Nome host del provider DNS privato"</string>
     <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"Inserisci il nome host del provider DNS"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index ead0bfb..a3e049c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -342,7 +342,7 @@
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"הוגדרה סיסמת גיבוי חדשה"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"הסיסמה החדשה והאישור אינם תואמים"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"הגדרת סיסמת גיבוי נכשלה"</string>
-    <string name="loading_injected_setting_summary" msgid="4095178591461231376">"טוען…"</string>
+    <string name="loading_injected_setting_summary" msgid="4095178591461231376">"בטעינה…"</string>
   <string-array name="color_mode_names">
     <item msgid="2425514299220523812">"דינמי (ברירת מחדל)"</item>
     <item msgid="8446070607501413455">"טבעי"</item>
@@ -405,7 +405,7 @@
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> עד לטעינה מלאה"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד לטעינה מלאה"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"לא ידוע"</string>
-    <string name="battery_info_status_charging" msgid="1705179948350365604">"טוען"</string>
+    <string name="battery_info_status_charging" msgid="1705179948350365604">"בטעינה"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"בטעינה"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"לא בטעינה"</string>
     <string name="battery_info_status_not_charging" msgid="8523453668342598579">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index e5d29bf..50543d8 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"<xliff:g id="TIME">%1$s</xliff:g> まで"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"<xliff:g id="TIME">%1$s</xliff:g>まで電池消費量を抑える"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"残り時間: <xliff:g id="THRESHOLD">%1$s</xliff:g>未満"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index e2d8e02..15e11db 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -226,7 +226,7 @@
   </string-array>
   <string-array name="enable_opengl_traces_entries">
     <item msgid="3191973083884253830">"없음"</item>
-    <item msgid="9089630089455370183">"로그캣"</item>
+    <item msgid="9089630089455370183">"Logcat"</item>
     <item msgid="5397807424362304288">"Systrace(그래픽)"</item>
     <item msgid="1340692776955662664">"glGetError의 스택 호출"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index ed88bc5..1cee8fb 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> based on your usage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> based on your usage"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Should last until about <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"ຈົນກວ່າຈະຮອດ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"ຂະຫຍາຍອາຍຸແບັດເຕີຣີກາຍ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"ຍັງເຫຼືອໜ້ອຍກວ່າ <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 8feb5ab..96aebc5 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -403,7 +403,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Iespējams, ierīce drīz izslēgsies (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Atlikušais laiks līdz pilnai uzlādei: <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>, kamēr pilnībā uzlādēts"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> līdz pilnīgai uzlādei"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Uzlāde"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"notiek uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index dda3462..d93304f 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -153,7 +153,7 @@
     <string name="launch_defaults_some" msgid="313159469856372621">"Поставени се некои стандардни вредности"</string>
     <string name="launch_defaults_none" msgid="4241129108140034876">"Нема поставено стандардни вредности"</string>
     <string name="tts_settings" msgid="8186971894801348327">"Поставки на текст-во-говор"</string>
-    <string name="tts_settings_title" msgid="1237820681016639683">"Излез текст-во-говор"</string>
+    <string name="tts_settings_title" msgid="1237820681016639683">"Претворање текст во говор"</string>
     <string name="tts_default_rate_title" msgid="6030550998379310088">"Брзина на говор"</string>
     <string name="tts_default_rate_summary" msgid="4061815292287182801">"Брзина со која се кажува текстот"</string>
     <string name="tts_default_pitch_title" msgid="6135942113172488671">"Интензитет"</string>
@@ -167,7 +167,7 @@
     <string name="tts_install_data_title" msgid="4264378440508149986">"Инсталирај гласовни податоци"</string>
     <string name="tts_install_data_summary" msgid="5742135732511822589">"Инсталирај ги гласовните податоци потребни за синтеза на говор"</string>
     <string name="tts_engine_security_warning" msgid="8786238102020223650">"Овој софтвер за синтеза на говор може да го собере сиот текст што ќе се говори, вклучувајќи и лични податоци како што се лозинки и броеви на кредитни картички. Тоа го прави апликацијата <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Употреби го овој софтвер за синтеза на говор?"</string>
-    <string name="tts_engine_network_required" msgid="1190837151485314743">"За овој јазик е потребно поврување со мрежа што функционира на излез за текст-во-говор."</string>
+    <string name="tts_engine_network_required" msgid="1190837151485314743">"За претворање текст во говор на овој јазик, потребна е активна мрежна врска."</string>
     <string name="tts_default_sample_string" msgid="4040835213373086322">"Ова е пример на синтеза на говор"</string>
     <string name="tts_status_title" msgid="7268566550242584413">"Статус на стандарден јазик"</string>
     <string name="tts_status_ok" msgid="1309762510278029765">"<xliff:g id="LOCALE">%1$s</xliff:g> е целосно поддржан"</string>
@@ -375,7 +375,7 @@
     <string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"Девтераномалија (слепило за црвена и зелена)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"Протаномалија (слепило за црвена и зелена)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"Тританомалија (слепило за сина и жолта)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Корекција на боја"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Корекција на бои"</string>
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Функцијата е експериментална и може да влијае на изведбата."</string>
     <string name="daltonizer_type_overridden" msgid="3116947244410245916">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4845022416859002011">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index f1934c3..b2e1cd6 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -153,7 +153,7 @@
     <string name="launch_defaults_some" msgid="313159469856372621">"केही पूर्वनिर्धारितहरू सेट गरिएका छन्"</string>
     <string name="launch_defaults_none" msgid="4241129108140034876">"कुनै पूर्वनिर्धारित सेट गरिएको छैन"</string>
     <string name="tts_settings" msgid="8186971894801348327">"पाठ-वाचन सेटिङहरू"</string>
-    <string name="tts_settings_title" msgid="1237820681016639683">"पाठवाचकको उत्पादन"</string>
+    <string name="tts_settings_title" msgid="1237820681016639683">"पाठवाचकको आउटपुट"</string>
     <string name="tts_default_rate_title" msgid="6030550998379310088">"वाणी दर"</string>
     <string name="tts_default_rate_summary" msgid="4061815292287182801">"पाठ वाचन हुने गति"</string>
     <string name="tts_default_pitch_title" msgid="6135942113172488671">"पिच"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 5725c22..f9e0d39 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -402,7 +402,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"ଖୁବ୍ ଶୀଘ୍ର ଟାବଲେଟ୍‌ଟି ବନ୍ଦ ହୋଇଯାଇପାରେ (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"ଖୁବ୍ ଶୀଘ୍ର ଡିଭାଇସ୍‌ଟି ବନ୍ଦ ହୋଇଯାଇପାରେ(<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ସମ୍ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାପାଇଁ <xliff:g id="TIME">%1$s</xliff:g> ଅବଶିଷ୍ଟ ଅଛି"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ସମ୍ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାପାଇଁ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index faccda7..b5acd8e 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -388,7 +388,7 @@
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ"</string>
     <string name="power_discharge_by" msgid="6453537733650125582">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਤੱਕ ਚੱਲੇਗੀ"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"<xliff:g id="TIME">%1$s</xliff:g> ਤੱਕ"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"ਬੈਟਰੀ ਲਾਈਫ਼ <xliff:g id="TIME">%1$s</xliff:g> ਤੋਂ ਬਾਅਦ ਤੱਕ ਵਧਾਓ"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"<xliff:g id="THRESHOLD">%1$s</xliff:g> ਤੋਂ ਘੱਟ ਸਮਾਂ ਬਾਕੀ"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 501cf50..c076d32 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -387,8 +387,8 @@
     <skip />
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização."</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g>."</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Até à(s) <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Prolongar a autonomia da bateria após <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Resta(m) menos de <xliff:g id="THRESHOLD">%1$s</xliff:g>."</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index e28825c..d77e1eb 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> – závisí to od intenzity využitia (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g> v závislosti od využitia (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g> v závislosti od využitia"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Mal by vydržať približne <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Podľa vášho využitia (<xliff:g id="LEVEL">%2$s</xliff:g>) vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Podľa vášho využitia vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Vydrží asi do <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Predĺžiť výdrž batérie minimálne do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Zostáva menej ako <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
@@ -402,8 +402,8 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"Tablet sa môže čoskoro vypnúť (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Zariadenie sa môže čoskoro vypnúť (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Zostávajúci čas do úplného nabitia: <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Nabíja sa"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"nabíja sa"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index b16d5d9..263b4d0 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -388,7 +388,7 @@
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Naprava bi morala glede na način uporabe (<xliff:g id="LEVEL">%2$s</xliff:g>) delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Naprava bi morala glede na način uporabe delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by" msgid="6453537733650125582">"Naprava bi morala delovati do približno <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Naprava bi morala delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Moralo bi zadostovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Podaljšanje časa delovanja akumulatorja za dlje kot <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Preostalo manj kot <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 57e690d..8f72ab6 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -264,7 +264,7 @@
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"வைஃபை இயங்கும் போதும் (வேகமான நெட்வொர்க் மாற்றத்திற்கு), மொபைல் டேட்டாவை எப்போதும் இயக்கத்தில் வைக்கும்."</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"வன்பொருள் விரைவுப்படுத்துதல் இணைப்பு முறை கிடைக்கும் போது, அதைப் பயன்படுத்தும்"</string>
     <string name="adb_warning_title" msgid="6234463310896563253">"USB பிழைதிருத்தத்தை அனுமதிக்கவா?"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB பிழைதிருத்தம் மேம்படுத்தல் நோக்கங்களுக்காக மட்டுமே. அதை உங்கள் கணினி மற்றும் சாதனத்திற்கு இடையில் தரவை நகலெடுக்கவும், அறிவிப்பு இல்லாமல் உங்கள் சாதனத்தில் பயன்பாடுகளை நிறுவவும், பதிவு தரவைப் படிக்கவும் பயன்படுத்தவும்."</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB பிழைதிருத்தம் மேம்படுத்தல் நோக்கங்களுக்காக மட்டுமே. அதை உங்கள் கணினி மற்றும் சாதனத்திற்கு இடையில் தரவை நகலெடுக்கவும், அறிவிப்பு இல்லாமல் உங்கள் சாதனத்தில் ஆப்ஸை நிறுவவும், பதிவு தரவைப் படிக்கவும் பயன்படுத்தவும்."</string>
     <string name="adb_keys_warning_message" msgid="5659849457135841625">"நீங்கள் ஏற்கனவே அனுமதித்த எல்லா கணினிகளிலிருந்தும் USB பிழைத்திருத்தத்திற்கான அணுகலைத் திரும்பப்பெற வேண்டுமா?"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"மேம்பட்ட அமைப்புகளை அனுமதிக்கவா?"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"இந்த அமைப்பு மேம்பட்டப் பயன்பாட்டிற்காக மட்டுமே. உங்கள் சாதனம் மற்றும் அதில் உள்ள பயன்பாடுகளைச் சிதைக்கும் அல்லது தவறாகச் செயல்படும் வகையில் பாதிப்பை ஏற்படுத்தும்."</string>
@@ -402,7 +402,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"டேப்லெட் விரைவில் ஆஃப் ஆகக்கூடும் (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"சாதனம் விரைவில் ஆஃப் ஆகக்கூடும் (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"முழு சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"முழு சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g>ம் ஆகும்"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"சார்ஜ் ஆகிறது"</string>
@@ -437,7 +437,7 @@
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"சாதன மொழிகளைப் பயன்படுத்து"</string>
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> க்கான அமைப்புகளைத் திறப்பதில் தோல்வி"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"இந்த உள்ளீட்டு முறையானது, கடவுச்சொற்கள் மற்றும் கிரெடிட் கார்டு எண்கள் போன்ற தனிப்பட்ட தகவல் உள்பட நீங்கள் உள்ளிடும் எல்லா உரையையும் சேகரிக்கக்கூடும். இது <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> பயன்பாட்டிலிருந்து வந்துள்ளது. இந்த உள்ளீட்டு முறையைப் பயன்படுத்தவா?"</string>
-    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"குறிப்பு: மறுதொடக்கம் செய்த பிறகு, மொபைலைத் திறக்கும் வரை இந்தப் பயன்பாட்டால் தொடங்க முடியாது"</string>
+    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"குறிப்பு: மறுதொடக்கம் செய்த பிறகு, மொபைலைத் திறக்கும் வரை இந்த ஆப்ஸால் தொடங்க முடியாது"</string>
     <string name="ims_reg_title" msgid="7609782759207241443">"IMS பதிவின் நிலை"</string>
     <string name="ims_reg_status_registered" msgid="933003316932739188">"பதிவு செய்யப்பட்டது"</string>
     <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"பதிவு செய்யப்படவில்லை"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 75344c4..e591121 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -414,7 +414,7 @@
     <string name="disabled" msgid="9206776641295849915">"నిలిపివేయబడింది"</string>
     <string name="external_source_trusted" msgid="2707996266575928037">"అనుమతించినవి"</string>
     <string name="external_source_untrusted" msgid="2677442511837596726">"అనుమతించబడలేదు"</string>
-    <string name="install_other_apps" msgid="6986686991775883017">"తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయండి"</string>
+    <string name="install_other_apps" msgid="6986686991775883017">"తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడం"</string>
     <string name="home" msgid="3256884684164448244">"సెట్టింగ్‌ల హోమ్"</string>
   <string-array name="battery_labels">
     <item msgid="8494684293649631252">"0%"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 0b184d2..b3bef9d 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -403,7 +403,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"อุปกรณ์อาจปิดเครื่องในไม่ช้า (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"อีก <xliff:g id="TIME">%1$s</xliff:g> จึงจะชาร์จเต็ม"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จเต็ม"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>จนกว่าจะชาร์จเต็ม"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"กำลังชาร์จ"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"กำลังชาร์จ"</string>
diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml
index 164aa93..4bc995f 100644
--- a/packages/SettingsLib/res/values-uz/arrays.xml
+++ b/packages/SettingsLib/res/values-uz/arrays.xml
@@ -241,7 +241,7 @@
     <item msgid="2355151170975410323">"“<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>” buyrug‘ida"</item>
   </string-array>
   <string-array name="debug_hw_overdraw_entries">
-    <item msgid="8190572633763871652">"YOQILMAGAN"</item>
+    <item msgid="8190572633763871652">"Yoqilmagan"</item>
     <item msgid="7688197031296835369">"Ortiqcha chizilgan joylarni ko‘rsatish"</item>
     <item msgid="2290859360633824369">"Muayyan rangdagi hududlarni ajratib belgilash"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 5dca475..0d3e8de 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -201,7 +201,7 @@
     <string name="vpn_settings_not_available" msgid="956841430176985598">"Ushbu foydalanuvchi uchun VPN sozlamalari mavjud emas"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"Ushbu foydalanuvchi uchun Modem rejimi sozlamalari mavjud emas"</string>
     <string name="apn_settings_not_available" msgid="7873729032165324000">"Ushbu foydalanuvchi uchun Internetga kirish nuqtasi (APN) sozlamalari mavjud emas"</string>
-    <string name="enable_adb" msgid="7982306934419797485">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="enable_adb" msgid="7982306934419797485">"USB orqali nosozliklarni aniqlash"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"USB orqali kompyuterga ulanganda tuzatish rejimi yoqilsin"</string>
     <string name="clear_adb_keys" msgid="4038889221503122743">"USB orqali nosozliklarni tuzatishni taqiqlash"</string>
     <string name="bugreport_in_power" msgid="7923901846375587241">"Xatoliklar hisoboti"</string>
@@ -264,7 +264,7 @@
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Mobil internet har doim yoniq tursin, hatto Wi-Fi yoniq bo‘lsa ham (bir tarmoqdan ikkinchisiga tezroq o‘tish uchun)."</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Modem rejimida apparatli tezlashtirishdan foydalanish (agar mavjud bo‘lsa)"</string>
     <string name="adb_warning_title" msgid="6234463310896563253">"USB orqali nosozliklarni tuzatishga ruxsat berilsinmi?"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB orqali nosozliklarni tuzatish faqat dasturlash maqsadlarida yoqiladi. Undan ma‘lumotlarni qurilmangiz va kompyuter o‘rtasida ko‘chirish, ilovalarni xabarnomasiz o‘rnatish va jurnal ma‘lumotlarini o‘qish uchun foydalaniladi."</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB orqali nosozliklarni aniqlash faqat dasturlash maqsadlarida yoqiladi. Undan maʼlumotlarni qurilmangiz va kompyuter o‘rtasida ko‘chirish, ilovalarni xabarnomasiz o‘rnatish va jurnal maʼlumotlarini o‘qish uchun foydalaniladi."</string>
     <string name="adb_keys_warning_message" msgid="5659849457135841625">"USB orqali nosozliklarni tuzatishga berilgan ruxsat siz hisobingizga kirgan barcha kompyuterlar uchun bekor qilinsinmi?"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"Dasturlash sozlamalariga ruxsat berilsinmi?"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"Bu sozlamalar faqat dasturlash maqsadlariga mo‘ljallangan. Shuning uchun, ular qurilmangizga va undagi ilovalariga shikast yetkazib, noto‘g‘ri ishlashiga sabab bo‘lishi mumkin."</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5c06c80..003c457 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -402,8 +402,8 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"Máy tính bảng có thể sắp tắt (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Thiết bị có thể sắp tắt (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Còn <xliff:g id="TIME">%1$s</xliff:g> cho tới khi được sạc đầy"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> cho tới khi được sạc đầy"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> nữa sẽ được sạc đầy"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> nữa sẽ được sạc đầy"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Đang sạc"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"đang sạc"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 460142c..bcf1f2d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -60,7 +60,7 @@
     <string name="speed_label_medium" msgid="3175763313268941953">"适中"</string>
     <string name="speed_label_fast" msgid="7715732164050975057">"快"</string>
     <string name="speed_label_very_fast" msgid="2265363430784523409">"很快"</string>
-    <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+    <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="6557104142667339895">"已断开连接"</string>
     <string name="bluetooth_disconnecting" msgid="8913264760027764974">"正在断开连接..."</string>
     <string name="bluetooth_connecting" msgid="8555009514614320497">"正在连接..."</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 84f5a04..98db7c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -27,6 +27,7 @@
 import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -799,10 +800,9 @@
                         == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
                     mDevice.getBluetoothClass().getDeviceClass()
                         == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
-                } else {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+                    EventLog.writeEvent(0x534e4554, "138529441", -1, "");
                 }
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 99d48d3..aac7fc3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -34,11 +34,14 @@
 
 import com.android.settingslib.R;
 
+import libcore.timezone.CountryTimeZones;
+import libcore.timezone.CountryTimeZones.TimeZoneMapping;
 import libcore.timezone.TimeZoneFinder;
 
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -387,7 +390,21 @@
 
         @VisibleForTesting
         public List<String> lookupTimeZoneIdsByCountry(String country) {
-            return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
+            final CountryTimeZones countryTimeZones =
+                    TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+            if (countryTimeZones == null) {
+                return null;
+            }
+            final List<TimeZoneMapping> mappings = countryTimeZones.getTimeZoneMappings();
+            return extractTimeZoneIds(mappings);
+        }
+
+        private static List<String> extractTimeZoneIds(List<TimeZoneMapping> timeZoneMappings) {
+            final List<String> zoneIds = new ArrayList<>(timeZoneMappings.size());
+            for (TimeZoneMapping timeZoneMapping : timeZoneMappings) {
+                zoneIds.add(timeZoneMapping.timeZoneId);
+            }
+            return Collections.unmodifiableList(zoneIds);
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
index eeb6cb0..ea8a62f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -49,6 +49,7 @@
     private final ArraySet<String> mWhitelistedApps = new ArraySet<>();
     private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>();
     private final ArraySet<String> mSysWhitelistedAppsExceptIdle = new ArraySet<>();
+    private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
 
     public PowerWhitelistBackend(Context context) {
         this(context, IDeviceIdleController.Stub.asInterface(
@@ -90,17 +91,7 @@
         // should be automatically whitelisted (otherwise user may be able to set restriction on
         // them, leading to bad device behavior.)
 
-        final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY);
-        final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
-                true /* updateIfNeeded */);
-        if (hasTelephony && defaultSms != null && TextUtils.equals(pkg,
-                defaultSms.getPackageName())) {
-            return true;
-        }
-
-        final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mAppContext);
-        if (hasTelephony && TextUtils.equals(pkg, defaultDialer)) {
+        if (mDefaultActiveApps.contains(pkg)) {
             return true;
         }
 
@@ -166,6 +157,7 @@
         mSysWhitelistedApps.clear();
         mSysWhitelistedAppsExceptIdle.clear();
         mWhitelistedApps.clear();
+        mDefaultActiveApps.clear();
         if (mDeviceIdleService == null) {
             return;
         }
@@ -183,6 +175,21 @@
             for (String app : sysWhitelistedAppsExceptIdle) {
                 mSysWhitelistedAppsExceptIdle.add(app);
             }
+            final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY);
+            final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
+                    true /* updateIfNeeded */);
+            final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(
+                    mAppContext);
+
+            if (hasTelephony) {
+                if (defaultSms != null) {
+                    mDefaultActiveApps.add(defaultSms.getPackageName());
+                }
+                if (!TextUtils.isEmpty(defaultDialer)) {
+                    mDefaultActiveApps.add(defaultDialer);
+                }
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
deleted file mode 100644
index fcf2363..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
+++ /dev/null
@@ -1,78 +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.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-/**
- * Framework mixin is deprecated, use the compat version instead.
- *
- * @deprecated
- */
-@Deprecated
-public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
-
-    private final PreferenceFragment mFragment;
-    private FooterPreference mFooterPreference;
-
-    public FooterPreferenceMixin(PreferenceFragment fragment, Lifecycle lifecycle) {
-        mFragment = fragment;
-        lifecycle.addObserver(this);
-    }
-
-    @Override
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        if (mFooterPreference != null) {
-            preferenceScreen.addPreference(mFooterPreference);
-        }
-    }
-
-    /**
-     * Creates a new {@link FooterPreference}.
-     */
-    public FooterPreference createFooterPreference() {
-        final PreferenceScreen screen = mFragment.getPreferenceScreen();
-        if (mFooterPreference != null && screen != null) {
-            screen.removePreference(mFooterPreference);
-        }
-        mFooterPreference = new FooterPreference(getPrefContext());
-
-        if (screen != null) {
-            screen.addPreference(mFooterPreference);
-        }
-        return mFooterPreference;
-    }
-
-    /**
-     * Returns an UI context with theme properly set for new Preference objects.
-     */
-    private Context getPrefContext() {
-        return mFragment.getPreferenceManager().getContext();
-    }
-
-    public boolean hasFooter() {
-        return mFooterPreference != null;
-    }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
deleted file mode 100644
index d45e56d..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
-
-    private final PreferenceFragmentCompat mFragment;
-    private FooterPreference mFooterPreference;
-
-    public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
-        mFragment = fragment;
-        lifecycle.addObserver(this);
-    }
-
-    @Override
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        if (mFooterPreference != null) {
-            preferenceScreen.addPreference(mFooterPreference);
-        }
-    }
-
-    /**
-     * Creates a new {@link FooterPreference}.
-     */
-    public FooterPreference createFooterPreference() {
-        final PreferenceScreen screen = mFragment.getPreferenceScreen();
-        if (mFooterPreference != null && screen != null) {
-            screen.removePreference(mFooterPreference);
-        }
-        mFooterPreference = new FooterPreference(getPrefContext());
-
-        if (screen != null) {
-            screen.addPreference(mFooterPreference);
-        }
-        return mFooterPreference;
-    }
-
-    /**
-     * Returns an UI context with theme properly set for new Preference objects.
-     */
-    private Context getPrefContext() {
-        return mFragment.getPreferenceManager().getContext();
-    }
-
-    public boolean hasFooter() {
-        return mFooterPreference != null;
-    }
-}
-
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
index 44ee423..3a571f9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -117,6 +117,8 @@
         final String testSms = "com.android.test.defaultsms";
         ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(testSms, "receiver"));
 
+        mPowerWhitelistBackend.refreshList();
+
         assertThat(mPowerWhitelistBackend.isWhitelisted(testSms)).isTrue();
         assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testSms)).isTrue();
     }
@@ -126,6 +128,8 @@
         final String testDialer = "com.android.test.defaultdialer";
         ShadowDefaultDialerManager.setDefaultDialerApplication(testDialer);
 
+        mPowerWhitelistBackend.refreshList();
+
         assertThat(mPowerWhitelistBackend.isWhitelisted(testDialer)).isTrue();
         assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testDialer)).isTrue();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
deleted file mode 100644
index 601da051..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
+++ /dev/null
@@ -1,100 +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.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class FooterPreferenceMixinCompatTest {
-
-    @Mock
-    private PreferenceFragmentCompat mFragment;
-    @Mock
-    private PreferenceScreen mScreen;
-
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-    private FooterPreferenceMixinCompat mMixin;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLifecycleOwner = () -> mLifecycle;
-        mLifecycle = new Lifecycle(mLifecycleOwner);
-        when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
-        when(mFragment.getPreferenceManager().getContext())
-                .thenReturn(RuntimeEnvironment.application);
-        mMixin = new FooterPreferenceMixinCompat(mFragment, mLifecycle);
-    }
-
-    @Test
-    public void createFooter_screenNotAvailable_noCrash() {
-        assertThat(mMixin.createFooterPreference()).isNotNull();
-    }
-
-    @Test
-    public void createFooter_screenAvailable_canAttachToScreen() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooter_screenAvailableDelayed_canAttachToScreen() {
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        mLifecycle.setPreferenceScreen(mScreen);
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooterTwice_screenAvailable_replaceOldFooter() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        mMixin.createFooterPreference();
-        mMixin.createFooterPreference();
-
-        verify(mScreen).removePreference(any(FooterPreference.class));
-        verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
deleted file mode 100644
index 7ae5d2d..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ /dev/null
@@ -1,101 +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.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class FooterPreferenceMixinTest {
-
-    @Mock
-    private PreferenceFragment mFragment;
-    @Mock
-    private PreferenceScreen mScreen;
-
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-    private FooterPreferenceMixin mMixin;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLifecycleOwner = () -> mLifecycle;
-        mLifecycle = new Lifecycle(mLifecycleOwner);
-        when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
-        when(mFragment.getPreferenceManager().getContext())
-                .thenReturn(RuntimeEnvironment.application);
-        mMixin = new FooterPreferenceMixin(mFragment, mLifecycle);
-    }
-
-    @Test
-    public void createFooter_screenNotAvailable_noCrash() {
-        assertThat(mMixin.createFooterPreference()).isNotNull();
-    }
-
-    @Test
-    public void createFooter_screenAvailable_canAttachToScreen() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooter_screenAvailableDelayed_canAttachToScreen() {
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        mLifecycle.setPreferenceScreen(mScreen);
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooterTwice_screenAvailable_replaceOldFooter() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        mMixin.createFooterPreference();
-        mMixin.createFooterPreference();
-
-        verify(mScreen).removePreference(any(FooterPreference.class));
-        verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
-    }
-
-}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 0144dd4..681b494 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -24,6 +24,8 @@
     // because this test is not an instrumentation test. (because the target runs in the system process.)
     srcs: [
         "test/**/*.java",
+        "src/android/provider/settings/backup/*",
+        "src/android/provider/settings/validators/*",
         "src/com/android/providers/settings/SettingsBackupAgent.java",
         "src/com/android/providers/settings/SettingsState.java",
         "src/com/android/providers/settings/SettingsHelper.java",
@@ -31,6 +33,8 @@
     static_libs: [
         "androidx.test.rules",
         "SettingsLibDisplayDensityUtils",
+        "platform-test-annotations",
+        "truth-prebuilt",
     ],
     libs: [
         "android.test.base",
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
new file mode 100644
index 0000000..890510f
--- /dev/null
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit": [
+        {
+            "name": "SettingsProviderTest"
+        },
+        {
+            "name": "CtsProviderTestCases",
+            "options": [
+                {
+                    "include-filter": "android.provider.cts.settings."
+                }
+            ]
+        }
+    ]
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
new file mode 100644
index 0000000..1527de1
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.provider.settings.backup;
+
+import android.provider.Settings;
+
+/** Information related to the backup of Global settings */
+public class GlobalSettings {
+
+    /**
+     * These keys may be mentioned in the SETTINGS_TO_BACKUP arrays in SystemSettings
+     * and SecureSettings as well.  This is because those tables drive both backup and
+     * restore, and restore needs to properly whitelist keys that used to live
+     * in those namespaces.
+     *
+     * NOTE: Settings are backed up and restored in the order they appear
+     *       in this array. If you have one setting depending on another,
+     *       make sure that they are ordered appropriately.
+     *
+     * NOTE: This table should only be used for settings which should be restored
+     *       between different types of devices
+     *       {@see #Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP}
+     *
+     * NOTE: All settings which are backed up should have a corresponding validator.
+     */
+    public static final String[] SETTINGS_TO_BACKUP = {
+        Settings.Global.APPLY_RAMPING_RINGER,
+        Settings.Global.BUGREPORT_IN_POWER_MENU,
+        Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
+        Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
+        Settings.Global.AUTO_TIME,
+        Settings.Global.AUTO_TIME_ZONE,
+        Settings.Global.POWER_SOUNDS_ENABLED,
+        Settings.Global.DOCK_SOUNDS_ENABLED,
+        Settings.Global.CHARGING_SOUNDS_ENABLED,
+        Settings.Global.USB_MASS_STORAGE_ENABLED,
+        Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+        Settings.Global.WIFI_WAKEUP_ENABLED,
+        Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+        Settings.Global.USE_OPEN_WIFI_PACKAGE,
+        Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+        Settings.Global.EMERGENCY_TONE,
+        Settings.Global.CALL_AUTO_RETRY,
+        Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
+        Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS,
+        Settings.Global.ENCODED_SURROUND_OUTPUT,
+        Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
+        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL,
+        Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+        Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+        Settings.Global.BLUETOOTH_ON,
+        Settings.Global.PRIVATE_DNS_MODE,
+        Settings.Global.PRIVATE_DNS_SPECIFIER,
+        Settings.Global.SOFT_AP_TIMEOUT_ENABLED,
+        Settings.Global.ZEN_DURATION,
+        Settings.Global.CHARGING_VIBRATION_ENABLED,
+        Settings.Global.AWARE_ALLOWED,
+        Settings.Global.NOTIFICATION_BUBBLES,
+    };
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
new file mode 100644
index 0000000..8c2e431
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.provider.settings.backup;
+
+import android.annotation.UnsupportedAppUsage;
+import android.provider.Settings;
+
+/** Information relating to the Secure settings which should be backed up */
+public class SecureSettings {
+
+    /**
+     * NOTE: Settings are backed up and restored in the order they appear
+     *       in this array. If you have one setting depending on another,
+     *       make sure that they are ordered appropriately.
+     */
+    @UnsupportedAppUsage
+    public static final String[] SETTINGS_TO_BACKUP = {
+        Settings.Secure.BUGREPORT_IN_POWER_MENU,                            // moved to global
+        Settings.Secure.ALLOW_MOCK_LOCATION,
+        Settings.Secure.USB_MASS_STORAGE_ENABLED,                           // moved to global
+        Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+        Settings.Secure.AUTOFILL_SERVICE,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+        Settings.Secure.ENABLED_VR_LISTENERS,
+        Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+        Settings.Secure.TOUCH_EXPLORATION_ENABLED,
+        Settings.Secure.ACCESSIBILITY_ENABLED,
+        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+        Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+        Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+        Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED,
+        Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
+        Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
+        Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
+        Settings.Secure.TTS_DEFAULT_RATE,
+        Settings.Secure.TTS_DEFAULT_PITCH,
+        Settings.Secure.TTS_DEFAULT_SYNTH,
+        Settings.Secure.TTS_ENABLED_PLUGINS,
+        Settings.Secure.TTS_DEFAULT_LOCALE,
+        Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+        Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,            // moved to global
+        Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,               // moved to global
+        Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT,                        // moved to global
+        Settings.Secure.MOUNT_PLAY_NOTIFICATION_SND,
+        Settings.Secure.MOUNT_UMS_AUTOSTART,
+        Settings.Secure.MOUNT_UMS_PROMPT,
+        Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
+        Settings.Secure.DOUBLE_TAP_TO_WAKE,
+        Settings.Secure.WAKE_GESTURE_ENABLED,
+        Settings.Secure.LONG_PRESS_TIMEOUT,
+        Settings.Secure.CAMERA_GESTURE_DISABLED,
+        Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
+        Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+        Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
+        Settings.Secure.PREFERRED_TTY_MODE,
+        Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
+        Settings.Secure.TTY_MODE_ENABLED,
+        Settings.Secure.RTT_CALLING_MODE,
+        Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+        Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
+        Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
+        Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+        Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
+        Settings.Secure.DISPLAY_WHITE_BALANCE_ENABLED,
+        Settings.Secure.SYNC_PARENT_SOUNDS,
+        Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
+        Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
+        Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
+        Settings.Secure.QS_TILES,
+        Settings.Secure.DOZE_ENABLED,
+        Settings.Secure.DOZE_ALWAYS_ON,
+        Settings.Secure.DOZE_PICK_UP_GESTURE,
+        Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
+        Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
+        Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
+        Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
+        Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+        Settings.Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN,
+        Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
+        Settings.Secure.SHOW_MEDIA_WHEN_BYPASSING,
+        Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
+        Settings.Secure.FACE_UNLOCK_APP_ENABLED,
+        Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+        Settings.Secure.ASSIST_GESTURE_ENABLED,
+        Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
+        Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
+        Settings.Secure.VR_DISPLAY_MODE,
+        Settings.Secure.NOTIFICATION_BADGING,
+        Settings.Secure.NOTIFICATION_BUBBLES,
+        Settings.Secure.NOTIFICATION_DISMISS_RTL,
+        Settings.Secure.QS_AUTO_ADDED_TILES,
+        Settings.Secure.SCREENSAVER_ENABLED,
+        Settings.Secure.SCREENSAVER_COMPONENTS,
+        Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+        Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+        Settings.Secure.LOCKDOWN_IN_POWER_MENU,
+        Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+        Settings.Secure.VOLUME_HUSH_GESTURE,
+        Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
+        Settings.Secure.HUSH_GESTURE_USED,
+        Settings.Secure.IN_CALL_NOTIFICATION_ENABLED,
+        Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+        Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+        Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
+        Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
+        Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
+        Settings.Secure.ZEN_DURATION,
+        Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION,
+        Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION,
+        Settings.Secure.ZEN_SETTINGS_UPDATED,
+        Settings.Secure.ZEN_SETTINGS_SUGGESTION_VIEWED,
+        Settings.Secure.CHARGING_SOUNDS_ENABLED,
+        Settings.Secure.CHARGING_VIBRATION_ENABLED,
+        Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
+        Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
+        Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL,
+        Settings.Secure.TRUST_AGENTS_EXTEND_UNLOCK,
+        Settings.Secure.UI_NIGHT_MODE,
+        Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
+        Settings.Secure.SKIP_GESTURE,
+        Settings.Secure.SKIP_DIRECTION,
+        Settings.Secure.SILENCE_GESTURE,
+        Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+        Settings.Secure.NAVIGATION_MODE,
+        Settings.Secure.AWARE_ENABLED,
+        Settings.Secure.SKIP_GESTURE_COUNT,
+        Settings.Secure.SKIP_TOUCH_COUNT,
+        Settings.Secure.SILENCE_ALARMS_GESTURE_COUNT,
+        Settings.Secure.SILENCE_CALL_GESTURE_COUNT,
+        Settings.Secure.SILENCE_TIMER_GESTURE_COUNT,
+        Settings.Secure.SILENCE_ALARMS_TOUCH_COUNT,
+        Settings.Secure.SILENCE_CALL_TOUCH_COUNT,
+        Settings.Secure.SILENCE_TIMER_TOUCH_COUNT,
+        Settings.Secure.DARK_MODE_DIALOG_SEEN,
+        Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED,
+        Settings.Secure.AWARE_LOCK_ENABLED
+    };
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
new file mode 100644
index 0000000..89b19de
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.provider.settings.backup;
+
+import android.annotation.UnsupportedAppUsage;
+import android.provider.Settings;
+
+/** Information about the system settings to back up */
+public class SystemSettings {
+
+    /**
+     * Settings to backup.
+     *
+     * NOTE: Settings are backed up and restored in the order they appear
+     *       in this array. If you have one setting depending on another,
+     *       make sure that they are ordered appropriately.
+     */
+    @UnsupportedAppUsage
+    public static final String[] SETTINGS_TO_BACKUP = {
+        Settings.System.STAY_ON_WHILE_PLUGGED_IN,   // moved to global
+        Settings.System.WIFI_USE_STATIC_IP,
+        Settings.System.WIFI_STATIC_IP,
+        Settings.System.WIFI_STATIC_GATEWAY,
+        Settings.System.WIFI_STATIC_NETMASK,
+        Settings.System.WIFI_STATIC_DNS1,
+        Settings.System.WIFI_STATIC_DNS2,
+        Settings.System.BLUETOOTH_DISCOVERABILITY,
+        Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+        Settings.System.FONT_SCALE,
+        Settings.System.DIM_SCREEN,
+        Settings.System.SCREEN_OFF_TIMEOUT,
+        Settings.System.SCREEN_BRIGHTNESS_MODE,
+        Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+        Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
+        Settings.System.ADAPTIVE_SLEEP,
+        Settings.System.VIBRATE_INPUT_DEVICES,
+        Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+        Settings.System.TEXT_AUTO_REPLACE,
+        Settings.System.TEXT_AUTO_CAPS,
+        Settings.System.TEXT_AUTO_PUNCTUATE,
+        Settings.System.TEXT_SHOW_PASSWORD,
+        Settings.System.AUTO_TIME,                  // moved to global
+        Settings.System.AUTO_TIME_ZONE,             // moved to global
+        Settings.System.TIME_12_24,
+        Settings.System.DATE_FORMAT,
+        Settings.System.DTMF_TONE_WHEN_DIALING,
+        Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+        Settings.System.HEARING_AID,
+        Settings.System.TTY_MODE,
+        Settings.System.MASTER_MONO,
+        Settings.System.MASTER_BALANCE,
+        Settings.System.SOUND_EFFECTS_ENABLED,
+        Settings.System.HAPTIC_FEEDBACK_ENABLED,
+        Settings.System.POWER_SOUNDS_ENABLED,       // moved to global
+        Settings.System.DOCK_SOUNDS_ENABLED,        // moved to global
+        Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
+        Settings.System.SHOW_WEB_SUGGESTIONS,
+        Settings.System.SIP_CALL_OPTIONS,
+        Settings.System.SIP_RECEIVE_CALLS,
+        Settings.System.POINTER_SPEED,
+        Settings.System.VIBRATE_WHEN_RINGING,
+        Settings.System.RINGTONE,
+        Settings.System.LOCK_TO_APP_ENABLED,
+        Settings.System.NOTIFICATION_SOUND,
+        Settings.System.ACCELEROMETER_ROTATION,
+        Settings.System.SHOW_BATTERY_PERCENT,
+        Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+        Settings.System.RING_VIBRATION_INTENSITY,
+        Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+        Settings.System.DISPLAY_COLOR_MODE,
+        Settings.System.ALARM_ALERT,
+        Settings.System.NOTIFICATION_LIGHT_PULSE,
+    };
+}
diff --git a/core/java/android/provider/settings/validators/ComponentNameListValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/ComponentNameListValidator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/ComponentNameListValidator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/ComponentNameListValidator.java
diff --git a/core/java/android/provider/settings/validators/DiscreteValueValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/DiscreteValueValidator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/DiscreteValueValidator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/DiscreteValueValidator.java
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
new file mode 100644
index 0000000..4a0ed6f
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -0,0 +1,153 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
+
+import android.media.AudioFormat;
+import android.os.BatteryManager;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Validators for Global settings
+ */
+public class GlobalSettingsValidators {
+    /**
+     * All settings in {@link Global.SETTINGS_TO_BACKUP} array *must* have a non-null validator,
+     * otherwise they won't be restored.
+     */
+    public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+
+    static {
+        VALIDATORS.put(Global.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.BUGREPORT_IN_POWER_MENU, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.STAY_ON_WHILE_PLUGGED_IN,
+                value -> {
+                    try {
+                        int val = Integer.parseInt(value);
+                        return (val == 0)
+                                || (val == BatteryManager.BATTERY_PLUGGED_AC)
+                                || (val == BatteryManager.BATTERY_PLUGGED_USB)
+                                || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_USB))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_USB
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_USB
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+                    } catch (NumberFormatException e) {
+                        return false;
+                    }
+                });
+        VALIDATORS.put(Global.AUTO_TIME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.AUTO_TIME_ZONE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.POWER_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.DOCK_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.USB_MASS_STORAGE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.NETWORK_RECOMMENDATIONS_ENABLED,
+                new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
+        VALIDATORS.put(Global.WIFI_WAKEUP_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.USE_OPEN_WIFI_PACKAGE,
+                value -> (value == null) || PACKAGE_NAME_VALIDATOR.validate(value));
+        VALIDATORS.put(Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(
+                Global.EMERGENCY_TONE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+        VALIDATORS.put(Global.CALL_AUTO_RETRY, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.DOCK_AUDIO_MEDIA_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS,
+                new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(
+                Global.ENCODED_SURROUND_OUTPUT,
+                new DiscreteValueValidator(new String[] {"0", "1", "2", "3"}));
+        VALIDATORS.put(
+                Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
+                value -> {
+                    try {
+                        String[] surroundFormats = TextUtils.split(value, ",");
+                        for (String format : surroundFormats) {
+                            int audioFormat = Integer.valueOf(format);
+                            boolean isSurroundFormat = false;
+                            for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
+                                if (sf == audioFormat) {
+                                    isSurroundFormat = true;
+                                    break;
+                                }
+                            }
+                            if (!isSurroundFormat) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    } catch (NumberFormatException e) {
+                        return false;
+                    }
+                });
+        VALIDATORS.put(
+                Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
+                new InclusiveIntegerRangeValidator(0, 100));
+        VALIDATORS.put(
+                Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
+                new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(Global.LOW_POWER_MODE_TRIGGER_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX, PERCENTAGE_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Global.AUTOMATIC_POWER_SAVE_MODE,
+                new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(
+                Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, PERCENTAGE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.BLUETOOTH_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.PRIVATE_DNS_MODE, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Global.PRIVATE_DNS_SPECIFIER, ANY_STRING_VALIDATOR);
+        VALIDATORS.put(Global.SOFT_AP_TIMEOUT_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.WIFI_SCAN_THROTTLE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.APP_AUTO_RESTRICTION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.WIFI_PNO_RECENCY_SORTING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.WIFI_LINK_PROBING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
+        VALIDATORS.put(
+                Global.POWER_BUTTON_VERY_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(Global.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveFloatRangeValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveFloatRangeValidator.java
new file mode 100644
index 0000000..1a0b88c
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveFloatRangeValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Validate a float value lies within a given (boundary inclusive) range.
+ *
+ * @hide
+ */
+final class InclusiveFloatRangeValidator implements Validator {
+    private final float mMin;
+    private final float mMax;
+
+    InclusiveFloatRangeValidator(float min, float max) {
+        mMin = min;
+        mMax = max;
+    }
+
+    @Override
+    public boolean validate(@Nullable String value) {
+        try {
+            final float floatValue = Float.parseFloat(value);
+            return floatValue >= mMin && floatValue <= mMax;
+        } catch (NumberFormatException | NullPointerException e) {
+            return false;
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveIntegerRangeValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
new file mode 100644
index 0000000..f9f8ce8
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/InclusiveIntegerRangeValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import android.annotation.Nullable;
+
+/**
+ * Validate an integer value lies within a given (boundary inclusive) range.
+ *
+ * @hide
+ */
+final class InclusiveIntegerRangeValidator implements Validator {
+    private final int mMin;
+    private final int mMax;
+
+    InclusiveIntegerRangeValidator(int min, int max) {
+        mMin = min;
+        mMax = max;
+    }
+
+    @Override
+    public boolean validate(@Nullable String value) {
+        try {
+            final int intValue = Integer.parseInt(value);
+            return intValue >= mMin && intValue <= mMax;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/provider/settings/validators/ListValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/ListValidator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/ListValidator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/ListValidator.java
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/PackageNameListValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/PackageNameListValidator.java
new file mode 100644
index 0000000..a883223
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/PackageNameListValidator.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+
+/**
+ * Validate a list of package names.
+ *
+ * @hide
+ */
+final class PackageNameListValidator extends ListValidator {
+    PackageNameListValidator(String separator) {
+        super(separator);
+    }
+
+    @Override
+    protected boolean isEntryValid(String entry) {
+        return entry != null;
+    }
+
+    @Override
+    protected boolean isItemValid(String item) {
+        return PACKAGE_NAME_VALIDATOR.validate(item);
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
new file mode 100644
index 0000000..f160edc6
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -0,0 +1,231 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.TILE_LIST_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
+
+import android.provider.Settings.Secure;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Validators for the Secure Settings.
+ */
+public class SecureSettingsValidators {
+    /**
+     * All settings in {@link Secure.SETTINGS_TO_BACKUP} and {@link
+     * Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null validator, otherwise
+     * they won't be restored.
+     */
+    public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+
+    static {
+        VALIDATORS.put(Secure.BUGREPORT_IN_POWER_MENU, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ALLOW_MOCK_LOCATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.USB_MASS_STORAGE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+                new DiscreteValueValidator(new String[] {"-1", "0", "11", "12", "13"}));
+        VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE));
+        VALIDATORS.put(
+                Secure.ENABLED_ACCESSIBILITY_SERVICES, COLON_SEPARATED_COMPONENT_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.ENABLED_VR_LISTENERS, COLON_SEPARATED_COMPONENT_LIST_VALIDATOR);
+        VALIDATORS.put(
+                Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                COLON_SEPARATED_COMPONENT_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.TOUCH_EXPLORATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
+        // technically either ComponentName or class name, but there's proper value
+        // validation at callsites, so allow any non-null string
+        VALIDATORS.put(Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, value -> value != null);
+        VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_CAPTIONING_PRESET,
+                new DiscreteValueValidator(new String[] {"-1", "0", "1", "2", "3", "4"}));
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_LOCALE, LOCALE_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
+                new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
+                new DiscreteValueValidator(
+                        new String[] {"DEFAULT", "MONOSPACE", "SANS_SERIF", "SERIF"}));
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
+                new InclusiveFloatRangeValidator(0.5f, 2.0f));
+        VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.TTS_DEFAULT_RATE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.TTS_DEFAULT_PITCH, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.TTS_DEFAULT_SYNTH, PACKAGE_NAME_VALIDATOR);
+        VALIDATORS.put(Secure.TTS_ENABLED_PLUGINS, new PackageNameListValidator(" "));
+        VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.MOUNT_PLAY_NOTIFICATION_SND, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MOUNT_UMS_AUTOSTART, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MOUNT_UMS_PROMPT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MOUNT_UMS_NOTIFY_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.PREFERRED_TTY_MODE,
+                new DiscreteValueValidator(new String[] {"0", "1", "2", "3"}));
+        VALIDATORS.put(Secure.ENHANCED_VOICE_PRIVACY_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.TTY_MODE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.RTT_CALLING_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.INCALL_POWER_BUTTON_BEHAVIOR,
+                new DiscreteValueValidator(new String[] {"1", "2"}));
+        VALIDATORS.put(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.NIGHT_DISPLAY_AUTO_MODE, new InclusiveIntegerRangeValidator(0, 2));
+        VALIDATORS.put(Secure.DISPLAY_WHITE_BALANCE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SYNC_PARENT_SOUNDS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.QS_TILES, TILE_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_ALWAYS_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_PICK_UP_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_DOUBLE_TAP_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_TAP_SCREEN_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DOZE_WAKE_DISPLAY_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.NFC_PAYMENT_DEFAULT_COMPONENT, COMPONENT_NAME_VALIDATOR);
+        VALIDATORS.put(
+                Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_UNLOCK_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
+        VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.QS_AUTO_ADDED_TILES, TILE_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_COMPONENTS, COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_DOCK, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCKDOWN_IN_POWER_MENU, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ENABLED_NOTIFICATION_LISTENERS,
+                COLON_SEPARATED_COMPONENT_LIST_VALIDATOR); // legacy restore setting
+        VALIDATORS.put(
+                Secure.ENABLED_NOTIFICATION_ASSISTANT,
+                COLON_SEPARATED_COMPONENT_LIST_VALIDATOR); // legacy restore setting
+        VALIDATORS.put(
+                Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
+                COLON_SEPARATED_PACKAGE_LIST_VALIDATOR); // legacy restore setting
+        VALIDATORS.put(Secure.HUSH_GESTURE_USED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SHOW_ZEN_SETTINGS_SUGGESTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ZEN_SETTINGS_UPDATED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.USER_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.TRUST_AGENTS_EXTEND_UNLOCK, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, JSON_OBJECT_VALIDATOR);
+        VALIDATORS.put(Secure.LOCK_SCREEN_WHEN_TRUST_LOST, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SKIP_GESTURE, BOOLEAN_VALIDATOR);
+        /*
+         * Only used if FeatureFlag "settings_skip_direction_mutable" is enabled.
+         * If feature flag is disabled, should assume SKIP_DIRECTION = 0.
+         *      0 / false = right to left to advance to next
+         *      1 / true = left to right to advance to next
+         */
+        VALIDATORS.put(Secure.SKIP_DIRECTION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
+        VALIDATORS.put(
+                Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+        VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_ALARMS_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_TIMER_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_CALL_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_ALARMS_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_TIMER_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SILENCE_CALL_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.ODI_CAPTIONS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.UI_NIGHT_MODE, new InclusiveIntegerRangeValidator(0, 2));
+        VALIDATORS.put(Secure.GLOBAL_ACTIONS_PANEL_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.AWARE_LOCK_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
new file mode 100644
index 0000000..224042c
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -0,0 +1,207 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * This class provides both interface for validation and common validators
+ * used to ensure Settings have meaningful values.
+ *
+ * @hide
+ */
+public class SettingsValidators {
+
+    public static final Validator BOOLEAN_VALIDATOR =
+            new DiscreteValueValidator(new String[] {"0", "1"});
+
+    public static final Validator ANY_STRING_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            return true;
+        }
+    };
+
+    public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            try {
+                return Integer.parseInt(value) >= 0;
+            } catch (NumberFormatException e) {
+                return false;
+            }
+        }
+    };
+
+    public static final Validator ANY_INTEGER_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            try {
+                Integer.parseInt(value);
+                return true;
+            } catch (NumberFormatException e) {
+                return false;
+            }
+        }
+    };
+
+    public static final Validator URI_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            try {
+                Uri.decode(value);
+                return true;
+            } catch (IllegalArgumentException e) {
+                return false;
+            }
+        }
+    };
+
+    /**
+     * Does not allow a setting to have a null {@link ComponentName}. Use {@link
+     * SettingsValidators#NULLABLE_COMPONENT_NAME_VALIDATOR} instead if a setting can have a
+     * nullable {@link ComponentName}.
+     */
+    public static final Validator COMPONENT_NAME_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            return value != null && ComponentName.unflattenFromString(value) != null;
+        }
+    };
+
+    /**
+     * Allows a setting to have a null {@link ComponentName}.
+     */
+    public static final Validator NULLABLE_COMPONENT_NAME_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            return value == null || COMPONENT_NAME_VALIDATOR.validate(value);
+        }
+    };
+
+    public static final Validator PACKAGE_NAME_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            return value != null && isStringPackageName(value);
+        }
+
+        private boolean isStringPackageName(String value) {
+            // The name may contain uppercase or lowercase letters ('A' through 'Z'), numbers,
+            // and underscores ('_'). However, individual package name parts may only
+            // start with letters.
+            // (https://developer.android.com/guide/topics/manifest/manifest-element.html#package)
+            if (value == null) {
+                return false;
+            }
+            String[] subparts = value.split("\\.");
+            boolean isValidPackageName = true;
+            for (String subpart : subparts) {
+                isValidPackageName &= isSubpartValidForPackageName(subpart);
+                if (!isValidPackageName) break;
+            }
+            return isValidPackageName;
+        }
+
+        private boolean isSubpartValidForPackageName(String subpart) {
+            if (subpart.length() == 0) return false;
+            boolean isValidSubpart = Character.isLetter(subpart.charAt(0));
+            for (int i = 1; i < subpart.length(); i++) {
+                isValidSubpart &= (Character.isLetterOrDigit(subpart.charAt(i))
+                                || (subpart.charAt(i) == '_'));
+                if (!isValidSubpart) break;
+            }
+            return isValidSubpart;
+        }
+    };
+
+    public static final Validator LENIENT_IP_ADDRESS_VALIDATOR = new Validator() {
+        private static final int MAX_IPV6_LENGTH = 45;
+
+        @Override
+        public boolean validate(@Nullable String value) {
+            if (value == null) {
+                return false;
+            }
+            return value.length() <= MAX_IPV6_LENGTH;
+        }
+    };
+
+    public static final Validator LOCALE_VALIDATOR = new Validator() {
+        @Override
+        public boolean validate(@Nullable String value) {
+            if (value == null) {
+                return false;
+            }
+            Locale[] validLocales = Locale.getAvailableLocales();
+            for (Locale locale : validLocales) {
+                if (value.equals(locale.toString())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */
+    public static final Validator JSON_OBJECT_VALIDATOR = (value) -> {
+        if (TextUtils.isEmpty(value)) {
+            return false;
+        }
+        try {
+            new JSONObject(value);
+            return true;
+        } catch (JSONException e) {
+            return false;
+        }
+    };
+
+    public static final Validator TTS_LIST_VALIDATOR = new TTSListValidator();
+
+    public static final Validator TILE_LIST_VALIDATOR = new TileListValidator();
+
+    static final Validator DATE_FORMAT_VALIDATOR = value -> {
+        try {
+            new SimpleDateFormat(value);
+            return true;
+        } catch (IllegalArgumentException | NullPointerException e) {
+            return false;
+        }
+    };
+
+    static final Validator COLON_SEPARATED_COMPONENT_LIST_VALIDATOR =
+            new ComponentNameListValidator(":");
+
+    static final Validator COLON_SEPARATED_PACKAGE_LIST_VALIDATOR =
+            new PackageNameListValidator(":");
+
+    static final Validator COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR =
+            new ComponentNameListValidator(",");
+
+    static final Validator PERCENTAGE_INTEGER_VALIDATOR =
+            new InclusiveIntegerRangeValidator(0, 100);
+
+    static final Validator VIBRATION_INTENSITY_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3);
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
new file mode 100644
index 0000000..94ab0f1
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.DATE_FORMAT_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.URI_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR;
+
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.hardware.display.ColorDisplayManager;
+import android.os.BatteryManager;
+import android.provider.Settings.System;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * Validators for System settings
+ */
+public class SystemSettingsValidators {
+    /**
+     * These are all public system settings
+     *
+     * <p>All settings in {@link System.SETTINGS_TO_BACKUP} array *must* have a non-null validator,
+     * otherwise they won't be restored.
+     */
+    @UnsupportedAppUsage
+    public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+
+    static {
+        VALIDATORS.put(
+                System.STAY_ON_WHILE_PLUGGED_IN,
+                value -> {
+                    try {
+                        int val = Integer.parseInt(value);
+                        return (val == 0)
+                                || (val == BatteryManager.BATTERY_PLUGGED_AC)
+                                || (val == BatteryManager.BATTERY_PLUGGED_USB)
+                                || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_USB))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_USB
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+                                || (val
+                                        == (BatteryManager.BATTERY_PLUGGED_AC
+                                                | BatteryManager.BATTERY_PLUGGED_USB
+                                                | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+                    } catch (NumberFormatException e) {
+                        return false;
+                    }
+                });
+        VALIDATORS.put(System.END_BUTTON_BEHAVIOR, new InclusiveIntegerRangeValidator(0, 3));
+        VALIDATORS.put(System.WIFI_USE_STATIC_IP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.BLUETOOTH_DISCOVERABILITY, new InclusiveIntegerRangeValidator(0, 2));
+        VALIDATORS.put(System.BLUETOOTH_DISCOVERABILITY_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(
+                System.NEXT_ALARM_FORMATTED,
+                new Validator() {
+                    private static final int MAX_LENGTH = 1000;
+
+                    @Override
+                    public boolean validate(String value) {
+                        // TODO: No idea what the correct format is.
+                        return value == null || value.length() < MAX_LENGTH;
+                    }
+                });
+        VALIDATORS.put(
+                System.FONT_SCALE,
+                value -> {
+                    try {
+                        return Float.parseFloat(value) >= 0;
+                    } catch (NumberFormatException | NullPointerException e) {
+                        return false;
+                    }
+                });
+        VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                System.DISPLAY_COLOR_MODE,
+                new Validator() {
+                    @Override
+                    public boolean validate(@Nullable String value) {
+                        // Assume the actual validation that this device can properly handle this
+                        // kind of
+                        // color mode further down in ColorDisplayManager / ColorDisplayService.
+                        try {
+                            final int setting = Integer.parseInt(value);
+                            final boolean isInFrameworkRange =
+                                    setting >= ColorDisplayManager.COLOR_MODE_NATURAL
+                                            && setting <= ColorDisplayManager.COLOR_MODE_AUTOMATIC;
+                            final boolean isInVendorRange =
+                                    setting >= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN
+                                            && setting
+                                                    <= ColorDisplayManager
+                                                            .VENDOR_COLOR_MODE_RANGE_MAX;
+                            return isInFrameworkRange || isInVendorRange;
+                        } catch (NumberFormatException | NullPointerException e) {
+                            return false;
+                        }
+                    }
+                });
+        VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255));
+        VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
+        VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR);
+        VALIDATORS.put(System.TEXT_AUTO_REPLACE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TEXT_AUTO_CAPS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TEXT_AUTO_PUNCTUATE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TEXT_SHOW_PASSWORD, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.AUTO_TIME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.AUTO_TIME_ZONE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SHOW_GTALK_SERVICE_STATUS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                System.WALLPAPER_ACTIVITY,
+                new Validator() {
+                    private static final int MAX_LENGTH = 1000;
+
+                    @Override
+                    public boolean validate(String value) {
+                        if (value != null && value.length() > MAX_LENGTH) {
+                            return false;
+                        }
+                        return ComponentName.unflattenFromString(value) != null;
+                    }
+                });
+        VALIDATORS.put(
+                System.TIME_12_24, new DiscreteValueValidator(new String[] {"12", "24", null}));
+        VALIDATORS.put(System.DATE_FORMAT, DATE_FORMAT_VALIDATOR);
+        VALIDATORS.put(System.SETUP_WIZARD_HAS_RUN, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.ACCELEROMETER_ROTATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.USER_ROTATION, new InclusiveIntegerRangeValidator(0, 3));
+        VALIDATORS.put(System.DTMF_TONE_WHEN_DIALING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SOUND_EFFECTS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.HAPTIC_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.POWER_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.DOCK_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SHOW_WEB_SUGGESTIONS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.WIFI_USE_STATIC_IP, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.ADVANCED_SETTINGS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SCREEN_AUTO_BRIGHTNESS_ADJ, new InclusiveFloatRangeValidator(-1, 1));
+        VALIDATORS.put(System.VIBRATE_INPUT_DEVICES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.MASTER_MONO, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.MASTER_BALANCE, new InclusiveFloatRangeValidator(-1.f, 1.f));
+        VALIDATORS.put(System.NOTIFICATIONS_USE_RING_VOLUME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.VIBRATE_IN_SILENT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.MEDIA_BUTTON_RECEIVER, COMPONENT_NAME_VALIDATOR);
+        VALIDATORS.put(System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.VIBRATE_WHEN_RINGING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.DTMF_TONE_TYPE_WHEN_DIALING, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.HEARING_AID, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.TTY_MODE, new InclusiveIntegerRangeValidator(0, 3));
+        VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.LOCKSCREEN_DISABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SIP_RECEIVE_CALLS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                System.SIP_CALL_OPTIONS,
+                new DiscreteValueValidator(new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"}));
+        VALIDATORS.put(System.SIP_ALWAYS, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SIP_ADDRESS_ONLY, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SIP_ASK_ME_EACH_TIME, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.POINTER_SPEED, new InclusiveFloatRangeValidator(-7, 7));
+        VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                System.EGG_MODE,
+                new Validator() {
+                    @Override
+                    public boolean validate(@Nullable String value) {
+                        try {
+                            return Long.parseLong(value) >= 0;
+                        } catch (NumberFormatException e) {
+                            return false;
+                        }
+                    }
+                });
+        VALIDATORS.put(System.WIFI_STATIC_IP, LENIENT_IP_ADDRESS_VALIDATOR);
+        VALIDATORS.put(System.WIFI_STATIC_GATEWAY, LENIENT_IP_ADDRESS_VALIDATOR);
+        VALIDATORS.put(System.WIFI_STATIC_NETMASK, LENIENT_IP_ADDRESS_VALIDATOR);
+        VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
+        VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
+        VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
+    }
+}
diff --git a/core/java/android/provider/settings/validators/TTSListValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/TTSListValidator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/TTSListValidator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/TTSListValidator.java
diff --git a/core/java/android/provider/settings/validators/TileListValidator.java b/packages/SettingsProvider/src/android/provider/settings/validators/TileListValidator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/TileListValidator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/TileListValidator.java
diff --git a/core/java/android/provider/settings/validators/Validator.java b/packages/SettingsProvider/src/android/provider/settings/validators/Validator.java
similarity index 100%
rename from core/java/android/provider/settings/validators/Validator.java
rename to packages/SettingsProvider/src/android/provider/settings/validators/Validator.java
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 50528a1..f545fa6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.SecureSettings;
+import android.provider.settings.backup.SystemSettings;
+import android.provider.settings.validators.GlobalSettingsValidators;
+import android.provider.settings.validators.SecureSettingsValidators;
+import android.provider.settings.validators.SystemSettingsValidators;
 import android.provider.settings.validators.Validator;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -542,7 +548,7 @@
         Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, Settings.System.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP);
         } finally {
             cursor.close();
         }
@@ -552,7 +558,7 @@
         Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, Settings.Secure.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP);
         } finally {
             cursor.close();
         }
@@ -562,7 +568,7 @@
         Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
                 null, null);
         try {
-            return extractRelevantValues(cursor, Settings.Global.SETTINGS_TO_BACKUP);
+            return extractRelevantValues(cursor, GlobalSettings.SETTINGS_TO_BACKUP);
         } finally {
             cursor.close();
         }
@@ -633,18 +639,18 @@
         final String[] whitelist;
         Map<String, Validator> validators = null;
         if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, Settings.Secure.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
                     Settings.Secure.LEGACY_RESTORE_SETTINGS,
                     Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
-            validators = Settings.Secure.VALIDATORS;
+            validators = SecureSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.System.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, Settings.System.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
                     Settings.System.LEGACY_RESTORE_SETTINGS);
-            validators = Settings.System.VALIDATORS;
+            validators = SystemSettingsValidators.VALIDATORS;
         } else if (contentUri.equals(Settings.Global.CONTENT_URI)) {
-            whitelist = ArrayUtils.concatElements(String.class, Settings.Global.SETTINGS_TO_BACKUP,
+            whitelist = ArrayUtils.concatElements(String.class, GlobalSettings.SETTINGS_TO_BACKUP,
                     Settings.Global.LEGACY_RESTORE_SETTINGS);
-            validators = Settings.Global.VALIDATORS;
+            validators = GlobalSettingsValidators.VALIDATORS;
         } else {
             throw new IllegalArgumentException("Unknown URI: " + contentUri);
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 00b2563..046ffc3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1114,6 +1114,9 @@
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 GlobalSettingsProto.Notification.SNOOZE_OPTIONS);
         dumpSetting(s, p,
+                Settings.Global.NOTIFICATION_BUBBLES,
+                GlobalSettingsProto.Notification.BUBBLES);
+        dumpSetting(s, p,
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                 GlobalSettingsProto.Notification.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS);
         dumpSetting(s, p,
@@ -2225,7 +2228,7 @@
                 Settings.Secure.NOTIFICATION_BADGING,
                 SecureSettingsProto.Notification.BADGING);
         dumpSetting(s, p,
-                Settings.Secure.NOTIFICATION_BUBBLES,
+                Settings.Global.NOTIFICATION_BUBBLES,
                 SecureSettingsProto.Notification.BUBBLES);
         dumpSetting(s, p,
                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
@@ -2239,9 +2242,6 @@
         dumpSetting(s, p,
                 Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
                 SecureSettingsProto.PackageVerifier.USER_CONSENT);
-        dumpSetting(s, p,
-                Settings.Secure.PACKAGE_VERIFIER_STATE,
-                SecureSettingsProto.PackageVerifier.STATE);
         p.end(packageVerifierToken);
 
         final long parentalControlToken = p.start(SecureSettingsProto.PARENTAL_CONTROL);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e492e28..720266a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -70,6 +70,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+import android.provider.settings.validators.SystemSettingsValidators;
 import android.provider.settings.validators.Validator;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -1520,14 +1521,14 @@
             return false;
         }
 
-        // Special cases for location providers (sigh).
-        if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
-            return updateLocationProvidersAllowedLocked(value, tag, owningUserId, makeDefault,
-                    forceNotify);
-        }
-
         // Mutate the value.
         synchronized (mLock) {
+            // Special cases for location providers (sigh).
+            if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+                return updateLocationProvidersAllowedLocked(value, tag, owningUserId, makeDefault,
+                        forceNotify);
+            }
+
             switch (operation) {
                 case MUTATION_OPERATION_INSERT: {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
@@ -1717,7 +1718,7 @@
     }
 
     private void validateSystemSettingValue(String name, String value) {
-        Validator validator = Settings.System.VALIDATORS.get(name);
+        Validator validator = SystemSettingsValidators.VALIDATORS.get(name);
         if (validator != null && !validator.validate(value)) {
             throw new IllegalArgumentException("Invalid value: " + value
                     + " for setting: " + name);
@@ -3185,7 +3186,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 182;
+            private static final int SETTINGS_VERSION = 183;
 
             private final int mUserId;
 
@@ -4202,19 +4203,7 @@
 
                 if (currentVersion == 173) {
                     // Version 173: Set the default value for Secure Settings: NOTIFICATION_BUBBLES
-
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
-                    final Setting bubblesSetting = secureSettings.getSettingLocked(
-                            Secure.NOTIFICATION_BUBBLES);
-
-                    if (bubblesSetting.isNull()) {
-                        secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                                getContext().getResources().getBoolean(
-                                        R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 174;
                 }
 
@@ -4338,14 +4327,9 @@
                 if (currentVersion == 179) {
                     // Version 178: Reset the default for Secure Settings: NOTIFICATION_BUBBLES
                     // This is originally set in version 173, however, the default value changed
-                    // so this step is to ensure the value is updated to the correct defaulte
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    // so this step is to ensure the value is updated to the correct default.
 
-                    secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                            getContext().getResources().getBoolean(
-                                    R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                    true, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 180;
                 }
 
@@ -4399,6 +4383,20 @@
                     currentVersion = 182;
                 }
 
+                if (currentVersion == 182) {
+                    // Remove secure bubble settings.
+                    getSecureSettingsLocked(userId).deleteSettingLocked(
+                            Secure.NOTIFICATION_BUBBLES);
+
+                    // Add global bubble settings.
+                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+                            true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 183;
+                }
+
                 // 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
new file mode 100644
index 0000000..3a7de18
--- /dev/null
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -0,0 +1,799 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.SecureSettings;
+import android.provider.settings.backup.SystemSettings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Tests that ensure appropriate settings are backed up. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsBackupTest {
+
+    /**
+     * see {@link com.google.android.systemui.power.EnhancedEstimatesGoogleImpl} for more details
+     */
+    public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
+            "hybrid_sysui_battery_warning_flags";
+
+    /**
+     * The following blacklists contain settings that should *not* be backed up and restored to
+     * another device.  As a general rule, anything that is not user configurable should be
+     * blacklisted (and conversely, things that *are* user configurable *should* be backed up)
+     */
+    private static final Set<String> BACKUP_BLACKLISTED_SYSTEM_SETTINGS =
+            newHashSet(
+                    Settings.System.ADVANCED_SETTINGS, // candidate for backup?
+                    Settings.System.ALARM_ALERT_CACHE, // internal cache
+                    Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
+                    Settings.System.EGG_MODE, // I am the lolrus
+                    Settings.System.END_BUTTON_BEHAVIOR, // bug?
+                    Settings.System
+                            .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, // candidate for backup?
+                    Settings.System.LOCKSCREEN_DISABLED, // ?
+                    Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
+                    Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
+                    Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
+                    Settings.System.POINTER_LOCATION, // backup candidate?
+                    Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
+                    Settings.System.RINGTONE_CACHE, // internal cache
+                    Settings.System.SCREEN_BRIGHTNESS, // removed in P
+                    Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
+                    Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
+                    Settings.System.SHOW_TOUCHES, // bug?
+                    Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
+                    Settings.System.SIP_ALWAYS, // value, not a setting
+                    Settings.System.SYSTEM_LOCALES, // bug?
+                    Settings.System.USER_ROTATION, // backup candidate?
+                    Settings.System.VIBRATE_IN_SILENT, // deprecated?
+                    Settings.System.VIBRATE_ON, // candidate for backup?
+                    Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
+                                                          // not change volume
+                    Settings.System.VOLUME_ALARM, // deprecated since API 2?
+                    Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
+                    Settings.System.VOLUME_MASTER, // candidate for backup?
+                    Settings.System.VOLUME_MUSIC, // deprecated since API 2?
+                    Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
+                    Settings.System.VOLUME_RING, // deprecated since API 2?
+                    Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
+                    Settings.System.VOLUME_VOICE, // deprecated since API 2?
+                    Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
+                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+                    Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
+                    Settings.System.PEAK_REFRESH_RATE // depends on hardware capabilities
+                    );
+
+    private static final Set<String> BACKUP_BLACKLISTED_GLOBAL_SETTINGS =
+            newHashSet(
+                    Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
+                    Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
+                    Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
+                    Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
+                    Settings.Global.ADB_ENABLED,
+                    Settings.Global.ADD_USERS_WHEN_LOCKED,
+                    Settings.Global.AIRPLANE_MODE_ON,
+                    Settings.Global.AIRPLANE_MODE_RADIOS,
+                    Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
+                    Settings.Global.ALARM_MANAGER_CONSTANTS,
+                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
+                    Settings.Global.ALWAYS_FINISH_ACTIVITIES,
+                    Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
+                    Settings.Global.ANIMATOR_DURATION_SCALE,
+                    Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+                    Settings.Global.ANOMALY_CONFIG,
+                    Settings.Global.ANOMALY_CONFIG_VERSION,
+                    Settings.Global.APN_DB_UPDATE_CONTENT_URL,
+                    Settings.Global.APN_DB_UPDATE_METADATA_URL,
+                    Settings.Global.APP_BINDING_CONSTANTS,
+                    Settings.Global.APP_IDLE_CONSTANTS,
+                    Settings.Global.APP_OPS_CONSTANTS,
+                    Settings.Global.APP_STANDBY_ENABLED,
+                    Settings.Global.APP_TIME_LIMIT_USAGE_SOURCE,
+                    Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE,
+                    Settings.Global.ASSISTED_GPS_ENABLED,
+                    Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+                    Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                    Settings.Global.AUTOFILL_LOGGING_LEVEL,
+                    Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
+                    Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+                    Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
+                    Settings.Global.AVERAGE_TIME_TO_DISCHARGE,
+                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+                    Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME,
+                    Settings.Global.BROADCAST_BG_CONSTANTS,
+                    Settings.Global.BROADCAST_FG_CONSTANTS,
+                    Settings.Global.BROADCAST_OFFLOAD_CONSTANTS,
+                    Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
+                    Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
+                    Settings.Global.BATTERY_SAVER_ADAPTIVE_DEVICE_SPECIFIC_CONSTANTS,
+                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
+                    Settings.Global.BATTERY_STATS_CONSTANTS,
+                    Settings.Global.BINDER_CALLS_STATS,
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+                    Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+                    Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS,
+                    Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
+                    Settings.Global.BLE_SCAN_BACKGROUND_MODE,
+                    Settings.Global.BLOCKED_SLICES,
+                    Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
+                    Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
+                    Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE,
+                    Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
+                    Settings.Global.BLUETOOTH_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX,
+                    Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
+                    Settings.Global.BLUETOOTH_DISABLED_PROFILES,
+                    Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST,
+                    Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX,
+                    Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
+                    Settings.Global.BOOT_COUNT,
+                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
+                    Settings.Global.CAPTIVE_PORTAL_HTTPS_URL,
+                    Settings.Global.CAPTIVE_PORTAL_HTTP_URL,
+                    Settings.Global.CAPTIVE_PORTAL_MODE,
+                    Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
+                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS,
+                    Settings.Global.CAPTIVE_PORTAL_SERVER,
+                    Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
+                    Settings.Global.CAPTIVE_PORTAL_USER_AGENT,
+                    Settings.Global.CAR_DOCK_SOUND,
+                    Settings.Global.CARRIER_APP_WHITELIST,
+                    Settings.Global.CARRIER_APP_NAMES,
+                    Settings.Global.CAR_UNDOCK_SOUND,
+                    Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                    Settings.Global.CDMA_ROAMING_MODE,
+                    Settings.Global.CDMA_SUBSCRIPTION_MODE,
+                    Settings.Global.CELL_ON,
+                    Settings.Global.CERT_PIN_UPDATE_CONTENT_URL,
+                    Settings.Global.CERT_PIN_UPDATE_METADATA_URL,
+                    Settings.Global.COMPATIBILITY_MODE,
+                    Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+                    Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE,
+                    Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+                    Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
+                    Settings.Global.CONVERSATION_ACTIONS_UPDATE_CONTENT_URL,
+                    Settings.Global.CONVERSATION_ACTIONS_UPDATE_METADATA_URL,
+                    Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
+                    Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+                    Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+                    Settings.Global.DATABASE_CREATION_BUILDID,
+                    Settings.Global.DATABASE_DOWNGRADE_REASON,
+                    Settings.Global.DATA_ROAMING,
+                    Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
+                    Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
+                    Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK,
+                    Settings.Global.DEBUG_APP,
+                    Settings.Global.DEBUG_VIEW_ATTRIBUTES,
+                    Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE,
+                    Settings.Global.DEFAULT_DNS_SERVER,
+                    Settings.Global.DEFAULT_INSTALL_LOCATION,
+                    Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
+                    Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
+                    Settings.Global.DESK_DOCK_SOUND,
+                    Settings.Global.DESK_UNDOCK_SOUND,
+                    Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
+                    Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+                    Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
+                    Settings.Global.DEVELOPMENT_FORCE_RTL,
+                    Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
+                    Settings.Global.DEVICE_DEMO_MODE,
+                    Settings.Global.DEVICE_IDLE_CONSTANTS,
+                    Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
+                    Settings.Global.BATTERY_SAVER_CONSTANTS,
+                    Settings.Global.BATTERY_TIP_CONSTANTS,
+                    Settings.Global.DEFAULT_SM_DP_PLUS,
+                    Settings.Global.DEVICE_NAME,
+                    Settings.Global.DEVICE_POLICY_CONSTANTS,
+                    Settings.Global.DEVICE_PROVISIONED,
+                    Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
+                    Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+                    Settings.Global.DISPLAY_PANEL_LPM,
+                    Settings.Global.DISPLAY_SCALING_FORCE,
+                    Settings.Global.DISPLAY_SIZE_FORCED,
+                    Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
+                    Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
+                    Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
+                    Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+                    Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
+                    Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
+                    Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
+                    Settings.Global.DROPBOX_AGE_SECONDS,
+                    Settings.Global.DROPBOX_MAX_FILES,
+                    Settings.Global.DROPBOX_QUOTA_KB,
+                    Settings.Global.DROPBOX_QUOTA_PERCENT,
+                    Settings.Global.DROPBOX_RESERVE_PERCENT,
+                    Settings.Global.DROPBOX_TAG_PREFIX,
+                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+                    Settings.Global.EMULATE_DISPLAY_CUTOUT,
+                    Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
+                    Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
+                    Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+                    Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
+                    Settings.Global.ENABLE_DISKSTATS_LOGGING,
+                    Settings.Global.ENABLE_EPHEMERAL_FEATURE,
+                    Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED,
+                    Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD,
+                    Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
+                    Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
+                    Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                    Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+                    Settings.Global.ERROR_LOGCAT_PREFIX,
+                    Settings.Global.EUICC_PROVISIONED,
+                    Settings.Global.EUICC_SUPPORTED_COUNTRIES,
+                    Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
+                    Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+                    Settings.Global.FANCY_IME_ANIMATIONS,
+                    Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
+                    Settings.Global.FORCED_APP_STANDBY_ENABLED,
+                    Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
+                    Settings.Global.WIFI_ON_WHEN_PROXY_DISCONNECTED,
+                    Settings.Global.FSTRIM_MANDATORY_INTERVAL,
+                    Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+                    Settings.Global.GLOBAL_HTTP_PROXY_HOST,
+                    Settings.Global.GLOBAL_HTTP_PROXY_PAC,
+                    Settings.Global.GLOBAL_HTTP_PROXY_PORT,
+                    Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
+                    Settings.Global.GNSS_SATELLITE_BLACKLIST,
+                    Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS,
+                    Settings.Global.HDMI_CEC_SWITCH_ENABLED,
+                    Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
+                    Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
+                    Settings.Global.HDMI_CONTROL_ENABLED,
+                    Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
+                    Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                    Settings.Global.HIDDEN_API_POLICY,
+                    Settings.Global.HIDE_ERROR_DIALOGS,
+                    Settings.Global.HTTP_PROXY,
+                    HYBRID_SYSUI_BATTERY_WARNING_FLAGS,
+                    Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
+                    Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
+                    Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
+                    Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL,
+                    Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
+                    Settings.Global.JOB_SCHEDULER_CONSTANTS,
+                    Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
+                    Settings.Global.JOB_SCHEDULER_TIME_CONTROLLER_CONSTANTS,
+                    Settings.Global.KEEP_PROFILE_IN_BACKGROUND,
+                    Settings.Global.KERNEL_CPU_THREAD_READER,
+                    Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
+                    Settings.Global.LANG_ID_UPDATE_METADATA_URL,
+                    Settings.Global.LAST_ACTIVE_USER_ID,
+                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+                    Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+                    Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
+                    Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
+                    Settings.Global.LOCK_SOUND,
+                    Settings.Global.LOOPER_STATS,
+                    Settings.Global.LOW_BATTERY_SOUND,
+                    Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
+                    Settings.Global.LOW_POWER_MODE,
+                    Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
+                    Settings.Global.LOW_POWER_MODE_STICKY,
+                    Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
+                    Settings.Global.LTE_SERVICE_FORCED,
+                    Settings.Global.LID_BEHAVIOR,
+                    Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
+                    Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+                    Settings.Global.MDC_INITIAL_MAX_RETRY,
+                    Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
+                    Settings.Global.MHL_POWER_CHARGE_ENABLED,
+                    Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
+                    Settings.Global.MOBILE_DATA, // Candidate for backup?
+                    Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                    Settings.Global.MODE_RINGER,
+                    Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_SMS_PROMPT,
+                    Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
+                    Settings.Global.MULTI_SIM_VOICE_PROMPT,
+                    Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
+                    Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_DEV_DELETE_AGE,
+                    Settings.Global.NETSTATS_DEV_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_DEV_ROTATE_AGE,
+                    Settings.Global.NETSTATS_ENABLED,
+                    Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES,
+                    Settings.Global.NETSTATS_POLL_INTERVAL,
+                    Settings.Global.NETSTATS_SAMPLE_ENABLED,
+                    Settings.Global.NETSTATS_AUGMENT_ENABLED,
+                    Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE,
+                    Settings.Global.NETSTATS_UID_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_UID_DELETE_AGE,
+                    Settings.Global.NETSTATS_UID_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_UID_ROTATE_AGE,
+                    Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION,
+                    Settings.Global.NETSTATS_UID_TAG_DELETE_AGE,
+                    Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES,
+                    Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
+                    Settings.Global.NETPOLICY_QUOTA_ENABLED,
+                    Settings.Global.NETPOLICY_QUOTA_UNLIMITED,
+                    Settings.Global.NETPOLICY_QUOTA_LIMITED,
+                    Settings.Global.NETPOLICY_QUOTA_FRAC_JOBS,
+                    Settings.Global.NETPOLICY_QUOTA_FRAC_MULTIPATH,
+                    Settings.Global.NETPOLICY_OVERRIDE_ENABLED,
+                    Settings.Global.NETWORK_AVOID_BAD_WIFI,
+                    Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES,
+                    Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+                    Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+                    Settings.Global.NETWORK_PREFERENCE,
+                    Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
+                    Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
+                    Settings.Global.NETWORK_SCORER_APP,
+                    Settings.Global.NETWORK_SCORING_PROVISIONED,
+                    Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT,
+                    Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS,
+                    Settings.Global.NETWORK_WATCHLIST_ENABLED,
+                    Settings.Global.NEW_CONTACT_AGGREGATOR,
+                    Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+                    Settings.Global.NITZ_UPDATE_DIFF,
+                    Settings.Global.NITZ_UPDATE_SPACING,
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                    Settings.Global.NSD_ON,
+                    Settings.Global.NTP_SERVER,
+                    Settings.Global.NTP_TIMEOUT,
+                    Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
+                    Settings.Global.OVERLAY_DISPLAY_DEVICES,
+                    Settings.Global.PAC_CHANGE_DELAY,
+                    Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+                    Settings.Global.PACKAGE_VERIFIER_ENABLE,
+                    Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
+                    Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
+                    Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
+                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT,
+                    Settings.Global.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
+                    Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS,
+                    Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+                    Settings.Global.POLICY_CONTROL,
+                    Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE,
+                    Settings.Global.POWER_MANAGER_CONSTANTS,
+                    Settings.Global.PREFERRED_NETWORK_MODE,
+                    Settings.Global.PRIVATE_DNS_DEFAULT_MODE,
+                    Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
+                    Settings.Global.RADIO_BLUETOOTH,
+                    Settings.Global.RADIO_CELL,
+                    Settings.Global.RADIO_NFC,
+                    Settings.Global.RADIO_WIFI,
+                    Settings.Global.RADIO_WIMAX,
+                    Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
+                    Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
+                    Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
+                    Settings.Global.SAFE_BOOT_DISALLOWED,
+                    Settings.Global.SELINUX_STATUS,
+                    Settings.Global.SELINUX_UPDATE_CONTENT_URL,
+                    Settings.Global.SELINUX_UPDATE_METADATA_URL,
+                    Settings.Global.SEND_ACTION_APP_ERROR,
+                    Settings.Global.SET_GLOBAL_HTTP_PROXY,
+                    Settings.Global.SET_INSTALL_LOCATION,
+                    Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL,
+                    Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST,
+                    Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL,
+                    Settings.Global.SETTINGS_USE_EXTERNAL_PROVIDER_API,
+                    Settings.Global.SETTINGS_USE_PSD_API,
+                    Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
+                    Settings.Global.SHOW_FIRST_CRASH_DIALOG,
+                    Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED,
+                    Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
+                    Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED,
+                    Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+                    Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
+                    Settings.Global.SHOW_TEMPERATURE_WARNING,
+                    Settings.Global.SHOW_USB_TEMPERATURE_ALARM,
+                    Settings.Global.SIGNED_CONFIG_VERSION,
+                    Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
+                    Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
+                    Settings.Global.SMS_OUTGOING_CHECK_INTERVAL_MS,
+                    Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
+                    Settings.Global.SMS_SHORT_CODE_CONFIRMATION,
+                    Settings.Global.SMS_SHORT_CODE_RULE,
+                    Settings.Global.SMS_SHORT_CODES_UPDATE_CONTENT_URL,
+                    Settings.Global.SMS_SHORT_CODES_UPDATE_METADATA_URL,
+                    Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
+                    Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
+                    Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS,
+                    Settings.Global.STORAGE_BENCHMARK_INTERVAL,
+                    Settings.Global.STORAGE_SETTINGS_CLOBBER_THRESHOLD,
+                    Settings.Global.SYNC_MANAGER_CONSTANTS,
+                    Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+                    Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+                    Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
+                    Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
+                    Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+                    Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
+                    Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+                    Settings.Global.SYS_VDSO,
+                    Settings.Global.SYS_UIDCPUPOWER,
+                    Settings.Global.SYS_TRACED,
+                    Settings.Global.FPS_DEVISOR,
+                    Settings.Global.TCP_DEFAULT_INIT_RWND,
+                    Settings.Global.TETHER_DUN_APN,
+                    Settings.Global.TETHER_DUN_REQUIRED,
+                    Settings.Global.TETHER_OFFLOAD_DISABLED,
+                    Settings.Global.TETHER_SUPPORTED,
+                    Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER,
+                    Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
+                    Settings.Global.TEXT_CLASSIFIER_ACTION_MODEL_PARAMS,
+                    Settings.Global.THEATER_MODE_ON,
+                    Settings.Global.TIME_ONLY_MODE_CONSTANTS,
+                    Settings.Global.TIME_REMAINING_ESTIMATE_MILLIS,
+                    Settings.Global.TIME_REMAINING_ESTIMATE_BASED_ON_USAGE,
+                    Settings.Global.TRANSITION_ANIMATION_SCALE,
+                    Settings.Global.TRUSTED_SOUND,
+                    Settings.Global.TZINFO_UPDATE_CONTENT_URL,
+                    Settings.Global.TZINFO_UPDATE_METADATA_URL,
+                    Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                    Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
+                    Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                    Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
+                    Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
+                    Settings.Global.UNGAZE_SLEEP_ENABLED,
+                    Settings.Global.UNLOCK_SOUND,
+                    Settings.Global.USE_GOOGLE_MAIL,
+                    Settings.Global.USER_ABSENT_RADIOS_OFF_FOR_SMALL_BATTERY_ENABLED,
+                    Settings.Global.USER_ABSENT_TOUCH_OFF_FOR_SMALL_BATTERY_ENABLED,
+                    Settings.Global.VT_IMS_ENABLED,
+                    Settings.Global.WAIT_FOR_DEBUGGER,
+                    Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+                    Settings.Global.GPU_DEBUG_APP,
+                    Settings.Global.GPU_DEBUG_LAYERS,
+                    Settings.Global.GPU_DEBUG_LAYERS_GLES,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
+                    Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
+                    Settings.Global.GAME_DRIVER_ALL_APPS,
+                    Settings.Global.GAME_DRIVER_OPT_IN_APPS,
+                    Settings.Global.GAME_DRIVER_PRERELEASE_OPT_IN_APPS,
+                    Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
+                    Settings.Global.GAME_DRIVER_BLACKLISTS,
+                    Settings.Global.GAME_DRIVER_BLACKLIST,
+                    Settings.Global.GAME_DRIVER_WHITELIST,
+                    Settings.Global.GAME_DRIVER_SPHAL_LIBRARIES,
+                    Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
+                    Settings.Global.GPU_DEBUG_LAYER_APP,
+                    Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
+                    Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT,
+                    Settings.Global.INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS,
+                    Settings.Global.USER_SWITCHER_ENABLED,
+                    Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
+                    Settings.Global.WARNING_TEMPERATURE,
+                    Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY,
+                    Settings.Global.WEBVIEW_FALLBACK_LOGIC_ENABLED,
+                    Settings.Global.WEBVIEW_MULTIPROCESS,
+                    Settings.Global.WEBVIEW_PROVIDER,
+                    Settings.Global.WFC_IMS_ENABLED,
+                    Settings.Global.WFC_IMS_MODE,
+                    Settings.Global.WFC_IMS_ROAMING_ENABLED,
+                    Settings.Global.WFC_IMS_ROAMING_MODE,
+                    Settings.Global.WIFI_ALWAYS_REQUESTED,
+                    Settings.Global.WIFI_BADGING_THRESHOLDS,
+                    Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
+                    Settings.Global.WIFI_COUNTRY_CODE,
+                    Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
+                    Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
+                    Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
+                    Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
+                    Settings.Global.WIFI_DISPLAY_ON,
+                    Settings.Global.WIFI_DISPLAY_WPS_CONFIG,
+                    Settings.Global.WIFI_ENHANCED_AUTO_JOIN,
+                    Settings.Global.WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS,
+                    Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+                    Settings.Global.WIFI_FREQUENCY_BAND,
+                    Settings.Global.WIFI_IDLE_MS,
+                    Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
+                    Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
+                    Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
+                    Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
+                    Settings.Global.WIFI_LINK_PROBING_ENABLED,
+                    Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+                    Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
+                    Settings.Global.WIFI_NETWORK_SHOW_RSSI,
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+                    Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT,
+                    Settings.Global.WIFI_ON,
+                    Settings.Global.WIFI_P2P_DEVICE_NAME,
+                    Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
+                    Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
+                    Settings.Global.WIFI_SAVED_STATE,
+                    Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+                    Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+                    Settings.Global.WIFI_SCAN_THROTTLE_ENABLED,
+                    Settings.Global.WIFI_SCORE_PARAMS,
+                    Settings.Global.WIFI_SLEEP_POLICY,
+                    Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+                    Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
+                    Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
+                    Settings.Global.WIFI_WATCHDOG_ON,
+                    Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                    Settings.Global.CHARGING_STARTED_SOUND,
+                    Settings.Global.WINDOW_ANIMATION_SCALE,
+                    Settings.Global.WTF_IS_FATAL,
+                    Settings.Global.ZEN_MODE,
+                    Settings.Global.ZEN_MODE_CONFIG_ETAG,
+                    Settings.Global.ZEN_MODE_RINGER_LEVEL,
+                    Settings.Global.ZRAM_ENABLED,
+                    Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION,
+                    Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+                    Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
+                    Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
+                    Settings.Global.BACKUP_MULTI_USER_ENABLED,
+                    Settings.Global.ISOLATED_STORAGE_LOCAL,
+                    Settings.Global.ISOLATED_STORAGE_REMOTE,
+                    Settings.Global.APPOP_HISTORY_PARAMETERS,
+                    Settings.Global.APPOP_HISTORY_MODE,
+                    Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER,
+                    Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
+                    Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+                    Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
+                    Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
+                    Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
+                    Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT,
+                    Settings.Global.POWER_BUTTON_LONG_PRESS,
+                    Settings.Global.POWER_BUTTON_VERY_LONG_PRESS);
+
+    private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
+             newHashSet(
+                 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, // Deprecated since O.
+                 Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
+                 Settings.Secure.ALWAYS_ON_VPN_APP,
+                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
+                 Settings.Secure.ANDROID_ID,
+                 Settings.Secure.ANR_SHOW_BACKGROUND,
+                 Settings.Secure.ASSISTANT,
+                 Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+                 Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
+                 Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
+                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
+                 Settings.Secure.ASSIST_STRUCTURE_ENABLED,
+                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE,
+                 Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH,
+                 Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH,
+                 Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_BYTES_CLEARED,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
+                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
+                 Settings.Secure.BACKUP_AUTO_RESTORE,
+                 Settings.Secure.BACKUP_ENABLED,
+                 Settings.Secure.BACKUP_PROVISIONED,
+                 Settings.Secure.BACKUP_TRANSPORT,
+                 Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
+                 Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
+                 Settings.Secure.CARRIER_APPS_HANDLED,
+                 Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+                 Settings.Secure.COMPLETED_CATEGORY_PREFIX,
+                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
+                 Settings.Secure.CONTENT_CAPTURE_ENABLED,
+                 Settings.Secure.DEFAULT_INPUT_METHOD,
+                 Settings.Secure.DEVICE_PAIRED,
+                 Settings.Secure.DIALER_DEFAULT_APPLICATION,
+                 Settings.Secure.DISABLED_PRINT_SERVICES,
+                 Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+                 Settings.Secure.DOCKED_CLOCK_FACE,
+                 Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
+                 Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                 Settings.Secure.ENABLED_INPUT_METHODS,  // Intentionally removed in P
+                 Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                 Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
+                 Settings.Secure.ENABLED_PRINT_SERVICES,
+                 Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE,
+                 Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED,
+                 Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                 Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
+                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
+                 Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
+                 Settings.Secure.INSTALL_NON_MARKET_APPS,
+                 Settings.Secure.LAST_SETUP_SHOWN,
+                 Settings.Secure.LOCATION_CHANGER,
+                 Settings.Secure.LOCATION_MODE,
+                 Settings.Secure.LOCATION_PERMISSIONS_UPGRADE_TO_Q_MODE,
+                 Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
+                 Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+                 Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
+                 Settings.Secure.MULTI_PRESS_TIMEOUT,
+                 Settings.Secure.NFC_PAYMENT_FOREGROUND,
+                 Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
+                 Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                 Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+                 Settings.Secure.ODI_CAPTIONS_ENABLED,
+                 Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
+                 Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
+                 Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
+                 Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+                 Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
+                 Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
+                 Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
+                 Settings.Secure.SEARCH_MAX_RESULTS_TO_DISPLAY,
+                 Settings.Secure.SEARCH_MAX_SHORTCUTS_RETURNED,
+                 Settings.Secure.SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS,
+                 Settings.Secure.SEARCH_MAX_STAT_AGE_MILLIS,
+                 Settings.Secure.SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING,
+                 Settings.Secure.SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING,
+                 Settings.Secure.SEARCH_NUM_PROMOTED_SOURCES,
+                 Settings.Secure.SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT,
+                 Settings.Secure.SEARCH_PREFILL_MILLIS,
+                 Settings.Secure.SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS,
+                 Settings.Secure.SEARCH_QUERY_THREAD_CORE_POOL_SIZE,
+                 Settings.Secure.SEARCH_QUERY_THREAD_MAX_POOL_SIZE,
+                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE,
+                 Settings.Secure.SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE,
+                 Settings.Secure.SEARCH_SOURCE_TIMEOUT_MILLIS,
+                 Settings.Secure.SEARCH_THREAD_KEEPALIVE_SECONDS,
+                 Settings.Secure.SEARCH_WEB_RESULTS_OVERRIDE_LIMIT,
+                 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                 Settings.Secure.SELECTED_SPELL_CHECKER,  // Intentionally removed in Q
+                 Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,  // Intentionally removed in Q
+                 Settings.Secure.SETTINGS_CLASSNAME,
+                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
+                 Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
+                 Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
+                 Settings.Secure.SLEEP_TIMEOUT,
+                 Settings.Secure.SMS_DEFAULT_APPLICATION,
+                 Settings.Secure.SPELL_CHECKER_ENABLED,  // Intentionally removed in Q
+                 Settings.Secure.TRUST_AGENTS_INITIALIZED,
+                 Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
+                 Settings.Secure.TV_INPUT_CUSTOM_LABELS,
+                 Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
+                 Settings.Secure.TV_USER_SETUP_COMPLETE,
+                 Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED,
+                 Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS,
+                 Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED,
+                 Settings.Secure.USER_SETUP_COMPLETE,
+                 Settings.Secure.USER_SETUP_PERSONALIZATION_STATE,
+                 Settings.Secure.VOICE_INTERACTION_SERVICE,
+                 Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                 Settings.Secure.INSTANT_APPS_ENABLED,
+                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
+                 Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS,
+                 Settings.Secure.KEYGUARD_SLICE_URI,
+                 Settings.Secure.PARENTAL_CONTROL_ENABLED,
+                 Settings.Secure.PARENTAL_CONTROL_REDIRECT_URL,
+                 Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
+                 Settings.Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT,
+                 Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
+                 Settings.Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION,
+                 Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
+                 Settings.Secure.FLASHLIGHT_AVAILABLE,
+                 Settings.Secure.FLASHLIGHT_ENABLED,
+                 Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
+                 Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS,
+                 Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS,
+                 Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
+                 Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
+                 Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
+                 Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED);
+
+    @Test
+    public void systemSettingsBackedUpOrBlacklisted() {
+        checkSettingsBackedUpOrBlacklisted(
+                getCandidateSettings(Settings.System.class),
+                newHashSet(SystemSettings.SETTINGS_TO_BACKUP),
+                BACKUP_BLACKLISTED_SYSTEM_SETTINGS);
+    }
+
+    @Test
+    public void globalSettingsBackedUpOrBlacklisted() {
+        checkSettingsBackedUpOrBlacklisted(
+                getCandidateSettings(Settings.Global.class),
+                newHashSet(GlobalSettings.SETTINGS_TO_BACKUP),
+                BACKUP_BLACKLISTED_GLOBAL_SETTINGS);
+    }
+
+    @Test
+    public void secureSettingsBackedUpOrBlacklisted() {
+        HashSet<String> keys = new HashSet<String>();
+        Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP);
+        Collections.addAll(keys, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+        checkSettingsBackedUpOrBlacklisted(
+                getCandidateSettings(Settings.Secure.class),
+                keys,
+                BACKUP_BLACKLISTED_SECURE_SETTINGS);
+    }
+
+    private static void checkSettingsBackedUpOrBlacklisted(
+            Set<String> settings, Set<String> settingsToBackup, Set<String> blacklist) {
+        Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
+        Set<String> settingsNotBackedUpOrBlacklisted = difference(settingsNotBackedUp, blacklist);
+        assertWithMessage("Settings not backed up or blacklisted")
+                .that(settingsNotBackedUpOrBlacklisted).isEmpty();
+
+        assertWithMessage("blacklisted settings backed up")
+                .that(intersect(settingsToBackup, blacklist)).isEmpty();
+    }
+
+    private static Set<String> getCandidateSettings(
+            Class<? extends Settings.NameValueTable> clazz) {
+        HashSet<String> result = new HashSet<String>();
+        for (Field field : clazz.getDeclaredFields()) {
+            if (looksLikeValidSetting(field)) {
+                try {
+                    result.add((String) field.get(null));
+                } catch (IllegalAccessException e) {
+                    // Impossible for public fields
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static boolean looksLikeValidSetting(Field field) {
+        int modifiers = field.getModifiers();
+        return isPublic(modifiers)
+                && isStatic(modifiers)
+                && isFinal(modifiers)
+                && field.getType() == String.class
+                && field.getAnnotation(Deprecated.class) == null;
+    }
+
+    private static <T> Set<T> difference(Set<T> s1, Set<T> s2) {
+        HashSet<T> result = new HashSet<T>(s1);
+        result.removeAll(s2);
+        return result;
+    }
+
+    private static <T> Set<T> intersect(Set<T> s1, Set<T> s2) {
+        HashSet<T> result = new HashSet<T>(s1);
+        result.retainAll(s2);
+        return result;
+    }
+
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
new file mode 100644
index 0000000..a3b0835
--- /dev/null
+++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -0,0 +1,343 @@
+/*
+ * 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 android.provider.settings.validators;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.SecureSettings;
+import android.provider.settings.backup.SystemSettings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/**
+ * Tests that ensure all backed up settings have non-null validators. Also, common validators
+ * are tested.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SettingsValidatorsTest {
+
+    @Test
+    public void testNonNegativeIntegerValidator() {
+        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("1"));
+        assertTrue(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("0"));
+        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("-1"));
+        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testNonNegativeIntegerValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testAnyIntegerValidator() {
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("1"));
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("0"));
+        assertTrue(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("-1"));
+        assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testAnyIntegerValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.ANY_INTEGER_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testUriValidator_onNullValue_returnsTrue() {
+        assertTrue(SettingsValidators.URI_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testComponentNameValidator() {
+        assertTrue(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(
+                "com.android.localtransport/.LocalTransport"));
+        assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testComponentNameValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.COMPONENT_NAME_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testLenientIpAddressValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testNullableComponentNameValidator_onValidComponentName_returnsTrue() {
+        assertTrue(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(
+                "com.android.localtransport/.LocalTransport"));
+    }
+
+    @Test
+    public void testNullableComponentNameValidator_onInvalidComponentName_returnsFalse() {
+        assertFalse(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(
+                "rectangle"));
+    }
+
+    @Test
+    public void testNullableComponentNameValidator_onNullValue_returnsTrue() {
+        assertTrue(SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testLocaleValidator() {
+        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("en_US"));
+        assertTrue(SettingsValidators.LOCALE_VALIDATOR.validate("es"));
+        assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate("rectangle"));
+    }
+
+    @Test
+    public void testLocaleValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.LOCALE_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testPackageNameValidator() {
+        assertTrue(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(
+                "com.google.android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate("com.google.@android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.android"));
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(".com.google.5android"));
+    }
+
+    @Test
+    public void testPackageNameValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.PACKAGE_NAME_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testDiscreteValueValidator() {
+        String[] beerTypes = new String[]{"Ale", "American IPA", "Stout"};
+        Validator v = new DiscreteValueValidator(beerTypes);
+        assertTrue(v.validate("Ale"));
+        assertTrue(v.validate("American IPA"));
+        assertTrue(v.validate("Stout"));
+        assertFalse(v.validate("Cider")); // just juice pretending to be beer
+    }
+
+    @Test
+    public void testDiscreteValueValidator_onNullValue_returnsFalse() {
+        String[] discreteTypes = new String[]{"Type1", "Type2"};
+        Validator v = new DiscreteValueValidator(discreteTypes);
+
+        assertFalse(v.validate(null));
+    }
+
+    @Test
+    public void testInclusiveIntegerRangeValidator() {
+        Validator v = new InclusiveIntegerRangeValidator(0, 5);
+        assertTrue(v.validate("0"));
+        assertTrue(v.validate("2"));
+        assertTrue(v.validate("5"));
+        assertFalse(v.validate("-1"));
+        assertFalse(v.validate("6"));
+    }
+
+    @Test
+    public void testInclusiveIntegerRangeValidator_onNullValue_returnsFalse() {
+        Validator v = new InclusiveIntegerRangeValidator(0, 5);
+
+        assertFalse(v.validate(null));
+    }
+
+    @Test
+    public void testInclusiveFloatRangeValidator() {
+        Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
+        assertTrue(v.validate("0.0"));
+        assertTrue(v.validate("2.0"));
+        assertTrue(v.validate("5.0"));
+        assertFalse(v.validate("-1.0"));
+        assertFalse(v.validate("6.0"));
+    }
+
+    @Test
+    public void testInclusiveFloatRangeValidator_onNullValue_returnsFalse() {
+        Validator v = new InclusiveFloatRangeValidator(0.0f, 5.0f);
+
+        assertFalse(v.validate(null));
+    }
+
+    @Test
+    public void testComponentNameListValidator() {
+        Validator v = new ComponentNameListValidator(",");
+        assertTrue(v.validate("com.android.localtransport/.LocalTransport,"
+                + "com.google.android.gms/.backup.migrate.service.D2dTransport"));
+        assertFalse(v.validate("com.google.5android,android"));
+    }
+
+    @Test
+    public void testComponentNameListValidator_onNullValue_returnsFalse() {
+        Validator v = new ComponentNameListValidator(",");
+
+        assertFalse(v.validate(null));
+    }
+
+    @Test
+    public void testPackageNameListValidator() {
+        Validator v = new PackageNameListValidator(",");
+        assertTrue(v.validate("com.android.localtransport.LocalTransport,com.google.android.gms"));
+        assertFalse(v.validate("5com.android.internal.backup.LocalTransport,android"));
+    }
+
+    @Test
+    public void testPackageNameListValidator_onNullValue_returnsFalse() {
+        Validator v = new PackageNameListValidator(",");
+
+        assertFalse(v.validate(null));
+    }
+
+    @Test
+    public void dateFormatValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.DATE_FORMAT_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testJSONObjectValidator() throws JSONException {
+        Validator v = SettingsValidators.JSON_OBJECT_VALIDATOR;
+
+        assertThat(v.validate(new JSONObject().toString())).isTrue();
+        assertThat(v.validate("{}")).isTrue();
+        assertThat(v.validate(new JSONObject().put("foo", "bar").toString()))
+                .isTrue();
+        assertThat(v.validate("{\"foo\": \"bar\"}")).isTrue();
+
+        assertThat(v.validate("random string")).isFalse();
+        assertThat(v.validate("random: string")).isFalse();
+        assertThat(v.validate("{random: }")).isFalse();
+    }
+
+    @Test
+    public void testJSONObjectValidator_onNullValue_returnsFalse() {
+        assertFalse(SettingsValidators.JSON_OBJECT_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testJSONObjectValidator_onEmptyString_returnsFalse() {
+        assertFalse(SettingsValidators.JSON_OBJECT_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void ensureAllBackedUpSystemSettingsHaveValidators() {
+        String offenders = getOffenders(concat(SystemSettings.SETTINGS_TO_BACKUP,
+                Settings.System.LEGACY_RESTORE_SETTINGS), SystemSettingsValidators.VALIDATORS);
+
+        failIfOffendersPresent(offenders, "Settings.System");
+    }
+
+    @Test
+    public void testTTSListValidator_withValidInput_returnsTrue() {
+        assertTrue(
+                SettingsValidators.TTS_LIST_VALIDATOR.validate(
+                        "com.foo.ttsengine:en-US,com.bar.ttsengine:es_ES"));
+    }
+
+    @Test
+    public void testTTSListValidator_withInvalidInput_returnsFalse() {
+        assertFalse(
+                SettingsValidators.TTS_LIST_VALIDATOR.validate(
+                        "com.foo.ttsengine:eng-USA,INVALID"));
+    }
+
+    @Test
+    public void testTTSListValidator_withEmptyInput_returnsFalse() {
+        assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(""));
+    }
+
+    @Test
+    public void testTTSListValidator_withNullInput_returnsFalse() {
+        assertFalse(SettingsValidators.TTS_LIST_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void testTileListValidator_withValidInput_returnsTrue() {
+        assertTrue(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,2,3,4"));
+    }
+
+    @Test
+    public void testTileListValidator_withMissingValue_returnsFalse() {
+        assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate("1,,3"));
+    }
+
+    @Test
+    public void testTileListValidator_withNullInput_returnsFalse() {
+        assertFalse(SettingsValidators.TILE_LIST_VALIDATOR.validate(null));
+    }
+
+    @Test
+    public void ensureAllBackedUpGlobalSettingsHaveValidators() {
+        String offenders = getOffenders(concat(GlobalSettings.SETTINGS_TO_BACKUP,
+                Settings.Global.LEGACY_RESTORE_SETTINGS), GlobalSettingsValidators.VALIDATORS);
+
+        failIfOffendersPresent(offenders, "Settings.Global");
+    }
+
+    @Test
+    public void ensureAllBackedUpSecureSettingsHaveValidators() {
+        String offenders = getOffenders(concat(SecureSettings.SETTINGS_TO_BACKUP,
+                Settings.Secure.LEGACY_RESTORE_SETTINGS), SecureSettingsValidators.VALIDATORS);
+
+        failIfOffendersPresent(offenders, "Settings.Secure");
+    }
+
+    private void failIfOffendersPresent(String offenders, String settingsType) {
+        if (offenders.length() > 0) {
+            fail("All " + settingsType + " settings that are backed up have to have a non-null"
+                    + " validator, but those don't: " + offenders);
+        }
+    }
+
+    private String getOffenders(String[] settingsToBackup, Map<String, Validator> validators) {
+        StringBuilder offenders = new StringBuilder();
+        for (String setting : settingsToBackup) {
+            if (validators.get(setting) == null) {
+                offenders.append(setting).append(" ");
+            }
+        }
+        return offenders.toString();
+    }
+
+    private String[] concat(String[] first, String[] second) {
+        if (second == null || second.length == 0) {
+            return first;
+        }
+        final int firstLen = first.length;
+        final int secondLen = second.length;
+        String[] both = new String[firstLen + secondLen];
+        System.arraycopy(first, 0, both, 0, firstLen);
+        System.arraycopy(second, 0, both, firstLen, secondLen);
+        return both;
+    }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
index 68efa67..ce1da4a 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/BaseSettingsProviderTest.java
@@ -31,6 +31,8 @@
 
 import libcore.io.Streams;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.runner.RunWith;
 
 import java.io.FileInputStream;
@@ -60,6 +62,20 @@
 
     private int mSecondaryUserId = Integer.MIN_VALUE;
 
+    @Before
+    public void setUp() {
+        Settings.Global.clearProviderForTest();
+        Settings.Secure.clearProviderForTest();
+        Settings.System.clearProviderForTest();
+    }
+
+    @After
+    public void tearDown() {
+        Settings.Global.clearProviderForTest();
+        Settings.Secure.clearProviderForTest();
+        Settings.System.clearProviderForTest();
+    }
+
     protected void setStringViaFrontEndApiSetting(int type, String name, String value, int userId) {
         ContentResolver contentResolver = getContext().getContentResolver();
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
index 863b035..ff11f70 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
@@ -84,8 +84,10 @@
         return line.trim();
     }
 
+    @Override
     @Before
     public void setUp() {
+        super.setUp();
         mUm = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
         mHasUserRestriction = mUm.hasUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
         mSystemSetUserRestriction = mUm.getUserRestrictionSource(
@@ -145,8 +147,10 @@
         assertTrue("Invalid value", value.equals("1") || value.equals("0"));
     }
 
+    @Override
     @After
     public void tearDown() {
+        super.tearDown();
         if (!mHasUserRestriction || mSystemSetUserRestriction) {
             // The test may have modified the user restriction state. Restore it.
             mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index cf8e1a5..57e22db 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -30,6 +30,7 @@
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.provider.Settings;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -47,6 +48,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -64,8 +66,10 @@
     private TestFriendlySettingsBackupAgent mAgentUnderTest;
     private Context mContext;
 
+    @Override
     @Before
     public void setUp() {
+        super.setUp();
         mContext = new ContextWithMockContentResolver(getContext());
 
         mAgentUnderTest = new TestFriendlySettingsBackupAgent();
@@ -268,5 +272,17 @@
             }
             return result;
         }
+
+        @Override
+        public Bundle call(String method, String request, Bundle args) {
+            for (Object[] resultRow : RESULT_ROWS) {
+                if (Objects.equals(request, resultRow[0])) {
+                    final Bundle res = new Bundle();
+                    res.putString("value", String.valueOf(resultRow[1]));
+                    return res;
+                }
+            }
+            return Bundle.EMPTY;
+        }
     }
 }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index 183f599..d67a9bc 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -33,6 +33,7 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -693,6 +694,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationModeChanges_viaFrontEndApi() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -735,6 +737,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowed_disableProviders() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -766,6 +769,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowed_enableAndDisable() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -788,6 +792,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowedLocked_invalidInput() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b2ff4b3..e767bcc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -172,6 +172,8 @@
     <!-- Permissions needed to test system only camera devices -->
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
+    <!-- Permissions needed for CTS camera test: RecordingTest.java when assuming shell id -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <!-- Permission needed to enable/disable Bluetooth/Wifi -->
     <uses-permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED" />
     <uses-permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED" />
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 8c0108d..602fe3e 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1264,9 +1264,7 @@
         }
         return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .addExtras(sNotificationBundle)
-                .setSmallIcon(
-                        isTv(context) ? R.drawable.ic_bug_report_black_24dp
-                                : com.android.internal.R.drawable.stat_sys_adb)
+                .setSmallIcon(R.drawable.ic_bug_report_black_24dp)
                 .setLocalOnly(true)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 4ba5146..d2f168e 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -52,7 +52,6 @@
 import com.android.internal.app.AlertController;
 
 import java.io.IOException;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -154,10 +153,7 @@
             if (which == mCursor.getCount() + mStaticItemCount) {
                 // The "Add new ringtone" item was clicked. Start a file picker intent to select
                 // only audio files (MIME type "audio/*")
-                final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
-                chooseFile.setType("audio/*");
-                chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
-                        new String[] { "audio/*", "application/ogg" });
+                final Intent chooseFile = getMediaFilePickerIntent();
                 startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
                 return;
             }
@@ -375,7 +371,8 @@
             setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
         }
         // If external storage is available, add a button to install sounds from storage.
-        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
             addNewSoundItem(listView);
         }
 
@@ -633,6 +630,18 @@
         return ringtoneManagerPos + mStaticItemCount;
     }
 
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[] { "audio/*", "application/ogg" });
+        return chooseFile;
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+    }
+
     private static class LocalizedCursor extends CursorWrapper {
 
         final int mTitleIndex;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4c52b132..0c582c4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -71,15 +71,21 @@
         "telephony-common",
     ],
 
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
     kotlincflags: ["-Xjvm-default=enable"],
 
     plugins: ["dagger2-compiler-2.19"],
 }
 
+filegroup {
+    name: "SystemUI-tests-utils",
+    srcs: [
+        "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java",
+        "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
+        "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
+    ],
+    path: "tests/src",
+}
+
 android_library {
     name: "SystemUI-tests",
     manifest: "tests/AndroidManifest.xml",
@@ -114,6 +120,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
+        "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
         "metrics-helper-lib",
@@ -132,7 +139,7 @@
     kotlincflags: ["-Xjvm-default=enable"],
     aaptflags: [
         "--extra-packages",
-        "com.android.keyguard:com.android.systemui",
+        "com.android.systemui",
     ],
     plugins: ["dagger2-compiler-2.19"],
 }
@@ -160,50 +167,6 @@
     kotlincflags: ["-Xjvm-default=enable"],
 
     dxflags: ["--multi-dex"],
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
     required: ["privapp_whitelist_com.android.systemui"],
 
 }
-
-// Only used for products that are shipping legacy Recents
-android_app {
-    name: "SystemUIWithLegacyRecents",
-    overrides: [
-        "SystemUI",
-    ],
-
-    platform_apis: true,
-    certificate: "platform",
-    privileged: true,
-
-    dxflags: ["--multi-dex"],
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
-    optimize: {
-        proguard_flags_files: ["proguard.flags", "legacy/recents/proguard.flags"],
-    },
-
-    static_libs: [
-        "SystemUI-core",
-    ],
-    libs: [
-        "telephony-common",
-    ],
-
-    kotlincflags: ["-Xjvm-default=enable"],
-
-    srcs: [
-        "legacy/recents/src/**/*.java",
-        "legacy/recents/src/**/I*.aidl",
-    ],
-    resource_dirs: [
-        "legacy/recents/res",
-    ],
-
-    manifest: "legacy/recents/AndroidManifest.xml",
-}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4f74605b..403e894 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -609,6 +609,10 @@
                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                 android:process=":ui"
                 android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSER" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
         </activity>
 
         <!-- Doze with notifications, run in main sysui process for every user  -->
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
index 488c465..de2ee9e 100644
--- a/packages/SystemUI/docs/physics-animation-layout.md
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -1,7 +1,11 @@
 # Physics Animation Layout
 
 ## Overview
-**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+**PhysicsAnimationLayout** works with implementations of **PhysicsAnimationController** to configure and run physics-based animations for each of its child views. During the initial construction of the animations, the layout queries the controller for basic configuration settings such as which properties to animate, which animations to chain together, and the default physics parameters to use.
+
+Once the animations are built, the controller can access **PhysicsPropertyAnimator** instances to run them. The animator behaves similarly to the familiar `ViewPropertyAnimator`, with the ability to animate `alpha`, `translation`, and `scale` values. It also supports additional functionality such as `followAnimatedTargetAlongPath` for more advanced motion.
+
+The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
 
 An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
 
@@ -27,7 +31,7 @@
 ### Animation Control Methods
 Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded```, ```onChildRemoved```, and ```setChildVisibility``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out/visible/gone. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
 
-In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`. 
+In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`.
 
 #### PhysicsPropertyAnimator
 
@@ -36,9 +40,14 @@
 - `translationX/Y/Z(float)`
 - `scaleX/Y(float)`
 
+...as well as shortcut methods to reduce the amount of boilerplate code needed for common use cases:
+- `position(float, float, Runnable…)`, which starts translationX and translationY animations, and calls the provided callbacks only when both animations have completed.
+- `followAnimatedTargetAlongPath(Path, int, TimeInterpolator)`, which animates a ‘target’ point along the given path using a traditional Animator. As the target moves, the translationX/Y physics animations are updated to follow the target, similarly to how they might follow a touch event location. This results in the view roughly following the path, but with natural motion that takes momentum into account. For example, if a path makes a 90 degree turn to the right, the physics animations will cause the view to curve naturally towards the new trajectory.
+
 It also provides the following configuration methods:
 - `withStartDelay(int)`, for starting the animation after a given delay.
 - `withStartVelocity(float)`, for starting the animation with the given start velocity.
+- `withStiffness(float)` and `withDampingRatio(float)`, for overriding the default physics param values returned by the controller’s getSpringForce method.
 - `withPositionStartVelocities(float, float)`, for setting specific start velocities for TRANSLATION_X and TRANSLATION_Y, since these typically differ.
 - `start(Runnable)`, to start the animation, with an optional end action to call when the animations for every property (including chained animations) have completed.
 
@@ -61,8 +70,7 @@
 
 - Often, animations will set starting values for properties before the animation begins. Property methods like `translationX` have an overloaded variant: `translationX(from, to)`. When `start()` is called, the animation will set the view's translationX property to `from` before beginning the animation to `to`.
 - We may want to use different end actions for each property. For example, if we're animating a view to the bottom of the screen, and also fading it out, we might want to perform an action as soon as the fade out is complete. We can use `alpha(to, endAction)`, which will call endAction as soon as the alpha animation is finished. A special case is `position(x, y, endAction)`, where the endAction is called when both translationX and translationY animations have completed.
-
-`PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
+- `PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
 
 ##### Examples
 Spring the stack of bubbles (whose animations are chained) to the bottom of the screen, shrinking them to 50% size. Once the first bubble is done shrinking, begin fading them out, and then remove them all from the parent once all bubbles have faded out:
@@ -93,16 +101,22 @@
     .startAll(removeFirstView);
 ```
 
+Move a view up along the left side of the screen, and then to the top right of the screen (assume a view that is currently halfway down the left side of the screen). When the translation animations have finished following the target, call a callback:
+
+```
+Path path = new Path();
+path.moveTo(view.getTranslationX(), view.getTranslationY());
+path.lineTo(view.getTranslationX(), 0);
+path.lineTo(mScreenWidth, 0);
+animationForChild(view)
+    .followAnimatedTargetAlongPath(path, 100, new LinearInterpolator())
+    .start(callbackAfterFollowingFinished);
+```
+
 ## PhysicsAnimationLayout
 The layout itself is a FrameLayout descendant with a few extra methods:
 
-```setController(PhysicsAnimationController controller)```
-Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+```setActiveController(PhysicsAnimationController controller)```
+Sets the given controller as the active controller for the layout. This causes the layout to construct or reconfigure the physics animations according to the new controller’s configuration methods, and halt any in-progress animations.
 
-```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
-Sets an end listener that is called when all animations on the given property have ended.
-
-```setMaxRenderedChildren(int max)```
-Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
-
-It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file
+Only the currently active controller is allowed to start animations. If a different controller is set as the active controller, the previous controller will no longer be able to start animations. Attempts to do so will have no effect. This is to ensure that multiple controllers aren’t updating animations at the same time, which can cause undefined behavior.
\ No newline at end of file
diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md
index 9fe2e18..2fb0c99 100644
--- a/packages/SystemUI/docs/plugin_hooks.md
+++ b/packages/SystemUI/docs/plugin_hooks.md
@@ -56,6 +56,11 @@
 
 Use: Allows replacement of the keyguard main clock.
 
+### Action: com.android.systemui.action.PLUGIN_NPV
+Expected interface: [NPVPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java)
+
+Use: Attach a view under QQS for prototyping.
+
 # Global plugin dependencies
 These classes can be accessed by any plugin using PluginDependency as long as they @Requires them.
 
diff --git a/packages/SystemUI/legacy/recents/AndroidManifest.xml b/packages/SystemUI/legacy/recents/AndroidManifest.xml
deleted file mode 100644
index 0d8b3cd..0000000
--- a/packages/SystemUI/legacy/recents/AndroidManifest.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (c) 2018 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          xmlns:tools="http://schemas.android.com/tools"
-          package="com.android.systemui"
-          android:sharedUserId="android.uid.systemui"
-          coreApp="true">
-
-    <application
-        android:name="com.android.systemui.SystemUIApplication">
-
-        <!-- Service used by secondary users to register themselves with the system user. -->
-        <service android:name=".recents.RecentsSystemUserService"
-            android:exported="false"
-            android:permission="com.android.systemui.permission.SELF" />
-
-        <!-- Alternate Recents -->
-        <activity android:name=".recents.RecentsActivity"
-                  android:label="@string/accessibility_desc_recent_apps"
-                  android:exported="false"
-                  android:launchMode="singleInstance"
-                  android:excludeFromRecents="true"
-                  android:stateNotNeeded="true"
-                  android:resumeWhilePausing="true"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
-                  android:theme="@style/RecentsTheme.Wallpaper">
-            <intent-filter>
-                <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
-            </intent-filter>
-        </activity>
-
-    </application>
-</manifest>
diff --git a/packages/SystemUI/legacy/recents/proguard.flags b/packages/SystemUI/legacy/recents/proguard.flags
deleted file mode 100644
index c358949..0000000
--- a/packages/SystemUI/legacy/recents/proguard.flags
+++ /dev/null
@@ -1,14 +0,0 @@
--keepclassmembers class ** {
-    public void onBusEvent(**);
-    public void onInterprocessBusEvent(**);
-}
--keepclassmembers class ** extends **.EventBus$InterprocessEvent {
-    public <init>(android.os.Bundle);
-}
-
--keep class com.android.systemui.recents.views.TaskView {
-    public int getDim();
-    public void setDim(int);
-    public float getTaskProgress();
-    public void setTaskProgress(float);
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_fast_toggle_app_home_exit.xml b/packages/SystemUI/legacy/recents/res/anim/recents_fast_toggle_app_home_exit.xml
deleted file mode 100644
index 69edcc7..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_fast_toggle_app_home_exit.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<!-- Recents Activity -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:duration="250"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_enter.xml b/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_enter.xml
deleted file mode 100644
index 00b3dfd..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="normal">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/linear_out_slow_in"
-         android:duration="150"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_exit.xml b/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_exit.xml
deleted file mode 100644
index 33831b8..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_from_launcher_exit.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@interpolator/recents_from_launcher_exit_interpolator"
-         android:duration="133"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_enter.xml b/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_enter.xml
deleted file mode 100644
index da1dee0..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/linear"
-         android:duration="200"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_exit.xml b/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_exit.xml
deleted file mode 100644
index 31cf26a..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_from_unknown_exit.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="normal">
-  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/fast_out_slow_in"
-         android:duration="200"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_bounce.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_bounce.xml
deleted file mode 100644
index 74f2814..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_bounce.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
-
-
-    <translate android:fromYDelta="0" android:toYDelta="2%"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/fast_out_slow_in"
-        android:duration="133"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="0.98"
-        android:fromYScale="1.0" android:toYScale="0.98"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@android:interpolator/fast_out_slow_in"
-        android:duration="133" />
-
-    <translate android:fromYDelta="0" android:toYDelta="-2%"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/recents_launch_prev_affiliated_task_bounce_ydelta"
-        android:startOffset="133"
-        android:duration="217"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="1.02040816326531"
-        android:fromYScale="1.0" android:toYScale="1.02040816326531"
-        android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-        android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@interpolator/recents_launch_next_affiliated_task_bounce_scale"
-        android:startOffset="133"
-        android:duration="217" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_source.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_source.xml
deleted file mode 100644
index f0fd684..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_source.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
-
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/accelerate_cubic"
-        android:duration="150"/>
-
-    <scale android:fromXScale="1.0" android:toXScale="0.9"
-        android:fromYScale="1.0" android:toYScale="0.9"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:pivotX="50%p" android:pivotY="50%p"
-        android:interpolator="@android:interpolator/fast_out_slow_in"
-        android:duration="300" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_target.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_target.xml
deleted file mode 100644
index 170ac82..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_next_affiliated_task_target.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
-
-    <translate android:fromYDelta="110%" android:toYDelta="0%"
-               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-               android:interpolator="@android:interpolator/decelerate_quint"
-               android:startOffset="50"
-               android:duration="250" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_bounce.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_bounce.xml
deleted file mode 100644
index b19167d..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_bounce.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
-
-    <translate android:fromYDelta="0%" android:toYDelta="10%"
-               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-               android:interpolator="@android:interpolator/fast_out_slow_in"
-               android:duration="133" />
-
-    <translate android:fromYDelta="0%" android:toYDelta="-10%"
-               android:fillEnabled="true" android:fillBefore="false" android:fillAfter="true"
-               android:interpolator="@interpolator/recents_launch_prev_affiliated_task_bounce_ydelta"
-               android:startOffset="133"
-               android:duration="217" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_source.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_source.xml
deleted file mode 100644
index ad5341b..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_source.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
-
-    <translate android:fromYDelta="0%" android:toYDelta="110%"
-               android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-               android:interpolator="@android:interpolator/accelerate_quint"
-               android:duration="300" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_target.xml b/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_target.xml
deleted file mode 100644
index 7687f02..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_launch_prev_affiliated_task_target.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
-
-    <alpha android:fromAlpha="0.6" android:toAlpha="1.0"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/decelerate_cubic"
-        android:startOffset="75"
-        android:duration="150"/>
-
-    <scale android:fromXScale="0.9" android:toXScale="1.0"
-        android:fromYScale="0.9" android:toYScale="1.0"
-        android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@android:interpolator/linear_out_slow_in"
-        android:pivotX="50%p" android:pivotY="50%p"
-        android:startOffset="75"
-        android:duration="225" />
-</set>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_enter.xml b/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_enter.xml
deleted file mode 100644
index 544ec88..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="normal">
-  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@interpolator/recents_to_launcher_enter_interpolator"
-         android:duration="133"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_exit.xml b/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_exit.xml
deleted file mode 100644
index 226edb8..0000000
--- a/packages/SystemUI/legacy/recents/res/anim/recents_to_launcher_exit.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-     android:shareInterpolator="false"
-     android:zAdjustment="top">
-  <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-         android:fillEnabled="true"
-         android:fillBefore="true" android:fillAfter="true"
-         android:interpolator="@android:interpolator/linear_out_slow_in"
-         android:duration="1"/>
-</set>
diff --git a/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_lower_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_lower_gradient.9.png
deleted file mode 100644
index 17100f7..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_lower_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_status_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_status_gradient.9.png
deleted file mode 100644
index e969d4c2..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-hdpi/recents_status_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_lower_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_lower_gradient.9.png
deleted file mode 100644
index b53bd8f..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_lower_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_status_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_status_gradient.9.png
deleted file mode 100644
index 657f710..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-mdpi/recents_status_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_lower_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_lower_gradient.9.png
deleted file mode 100644
index 09606f6..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_lower_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_status_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_status_gradient.9.png
deleted file mode 100644
index a444c55..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-xhdpi/recents_status_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_lower_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_lower_gradient.9.png
deleted file mode 100644
index 427cad9..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_lower_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_status_gradient.9.png b/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_status_gradient.9.png
deleted file mode 100644
index 29cf44b..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_status_gradient.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_task_shadow.9.png b/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_task_shadow.9.png
deleted file mode 100644
index 36e7e45..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable-xxhdpi/recents_task_shadow.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_dark.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_dark.xml
deleted file mode 100644
index b837ebe..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_dark.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-android:width="24dp"
-android:height="24dp"
-android:viewportWidth="24"
-android:viewportHeight="24">
-
-<path
-    android:fillColor="@color/recents_task_bar_dark_icon_color"
-    android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
-7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
-1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
-<path
-    android:pathData="M0 0h24v24H0z" />
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_light.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_light.xml
deleted file mode 100644
index 2b20814..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_dismiss_light.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-android:width="24dp"
-android:height="24dp"
-android:viewportWidth="24"
-android:viewportHeight="24">
-
-<path
-    android:fillColor="@color/recents_task_bar_light_icon_color"
-    android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
-7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
-1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
-<path
-    android:pathData="M0 0h24v24H0z" />
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_empty.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_empty.xml
deleted file mode 100644
index 5506de1..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_empty.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="100dp"
-    android:height="132dp"
-    android:viewportWidth="100"
-    android:viewportHeight="132">
-
-    <path
-        android:fillColor="#5AFFFFFF"
-        android:pathData="M86.91,68.67H13.09c-4.96,0-9,4.04-9,9V119c0,4.96,4.04,9,9,9h73.82c4.96,0,9-4.04,9-9V77.67
-C95.91,72.7,91.87,68.67,86.91,68.67z M27.59,77.27h26.72v3.94H27.59V77.27z
-M18.73,74.74c2.49,0,4.5,2.01,4.5,4.5
-c0,2.49-2.01,4.5-4.5,4.5s-4.5-2.01-4.5-4.5C14.23,76.75,16.24,74.74,18.73,74.74z
-M89.91,119c0,1.65-1.35,3-3,3H13.09 c-1.65,0-3-1.35-3-3V88.67h79.82V119z" />
-    <path
-        android:fillColor="#5AFFFFFF"
-        android:pathData="M86.91,36.3H13.09c-4.96,0-9,4.04-9,9v23c1.65-1.58,3.71-2.73,6-3.28v-9.08h79.82v9.08
-c2.29,0.55,4.35,1.69,6,3.28v-23C95.91,40.34,91.87,36.3,86.91,36.3z
-M18.73,51.38c-2.49,0-4.5-2.01-4.5-4.5s2.01-4.5,4.5-4.5
-s4.5,2.01,4.5,4.5S21.22,51.38,18.73,51.38z M54.31,48.84H27.59v-3.94h26.72V48.84z" />
-    <path
-        android:fillColor="#5AFFFFFF"
-        android:pathData="M86.91,4H13.09c-4.96,0-9,4.04-9,9v22.94c1.65-1.58,3.71-2.73,6-3.28V24h79.82v8.67
-c2.29,0.55,4.35,1.69,6,3.28V13C95.91,8.04,91.87,4,86.91,4z
-M18.73,18.5c-2.49,0-4.5-2.01-4.5-4.5s2.01-4.5,4.5-4.5
-s4.5,2.01,4.5,4.5S21.22,18.5,18.73,18.5z M54.31,15.97H27.59v-3.94h26.72V15.97z" />
-    <path
-        android:pathData="M 0 0 H 100 V 132 H 0 V 0 Z" />
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_grid_task_view_focus_frame_background.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_grid_task_view_focus_frame_background.xml
deleted file mode 100644
index 4987f9b..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_grid_task_view_focus_frame_background.xml
+++ /dev/null
@@ -1,19 +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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <solid android:color="#61FFFFFF" />
-  <corners android:radius="@dimen/recents_grid_task_view_focused_frame_rounded_corners_radius"/>
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_info_dark.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_info_dark.xml
deleted file mode 100644
index 555a69a..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_info_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="@color/recents_task_bar_dark_icon_color"
-        android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"/>
-</vector>
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_info_light.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_info_light.xml
deleted file mode 100644
index 65e7bf5..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_info_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM13.000000,17.000000l-2.000000,0.000000l0.000000,-6.000000l2.000000,0.000000L13.000000,17.000000zM13.000000,9.000000l-2.000000,0.000000L11.000000,7.000000l2.000000,0.000000L13.000000,9.000000z"
-        android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_app_pin.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_app_pin.xml
deleted file mode 100644
index 317f858..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_app_pin.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-
-    <path
-        android:fillColor="#FFffffff"
-        android:pathData="M16.000000,12.000000L16.000000,4.000000l1.000000,0.000000L17.000000,2.000000L7.000000,2.000000l0.000000,2.000000l1.000000,0.000000l0.000000,8.000000l-2.000000,2.000000l0.000000,2.000000l5.200000,0.000000l0.000000,6.000000l1.600000,0.000000l0.000000,-6.000000L18.000000,16.000000l0.000000,-2.000000L16.000000,12.000000z"/>
-</vector>
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_task_button_bg.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_task_button_bg.xml
deleted file mode 100644
index 8a8164a..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_lock_to_task_button_bg.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-     android:color="#ff9cdfd9">
-     <item>
-          <shape android:shape="oval">
-               <solid android:color="#9cc8c4" />
-               <size android:width="@dimen/recents_lock_to_app_size"
-                     android:height="@dimen/recents_lock_to_app_size" />
-          </shape>
-     </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_low_ram_stack_button_background.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_low_ram_stack_button_background.xml
deleted file mode 100644
index bff97f6..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_low_ram_stack_button_background.xml
+++ /dev/null
@@ -1,22 +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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" >
-
-      <corners android:radius="@dimen/borderless_button_radius" />
-
-      <solid android:color="?attr/clearAllBackgroundColor" />
-
-</shape>
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_dark.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_dark.xml
deleted file mode 100644
index fd468c1..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_dark.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <group
-            android:translateX="-252.000000"
-            android:translateY="-602.000000">
-        <group
-                android:translateX="109.000000"
-                android:translateY="514.000000">
-            <group
-                    android:translateX="144.000000"
-                    android:translateY="89.000000">
-                <path
-                    android:strokeColor="@color/recents_task_bar_dark_icon_color"
-                    android:strokeWidth="2"
-                    android:pathData="M17,17 L5,17 L5,5 L17,5 L17,17 Z" />
-            </group>
-        </group>
-    </group>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_light.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_light.xml
deleted file mode 100644
index 53229063..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_move_task_fullscreen_light.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--
-Copyright (C) 2015 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <group
-            android:translateX="-252.000000"
-            android:translateY="-602.000000">
-        <group
-                android:translateX="109.000000"
-                android:translateY="514.000000">
-            <group
-                    android:translateX="144.000000"
-                    android:translateY="89.000000">
-                <path
-                    android:strokeColor="@color/recents_task_bar_light_icon_color"
-                    android:strokeWidth="2"
-                    android:pathData="M19,19 L3,19 L3,3 L19,3 L19,5 L19,18 L19,19 Z" />
-            </group>
-        </group>
-    </group>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/drawable/recents_stack_action_background.xml b/packages/SystemUI/legacy/recents/res/drawable/recents_stack_action_background.xml
deleted file mode 100644
index 2a40dd0..0000000
--- a/packages/SystemUI/legacy/recents/res/drawable/recents_stack_action_background.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2016 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-        android:color="?android:attr/colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <shape>
-            <corners android:radius="@dimen/recents_task_view_rounded_corners_radius" />
-            <solid android:color="@android:color/white" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/interpolator/recents_from_launcher_exit_interpolator.xml b/packages/SystemUI/legacy/recents/res/interpolator/recents_from_launcher_exit_interpolator.xml
deleted file mode 100644
index 4a7fff6..0000000
--- a/packages/SystemUI/legacy/recents/res/interpolator/recents_from_launcher_exit_interpolator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:controlX1="0"
-    android:controlY1="0"
-    android:controlX2="0.8"
-    android:controlY2="1" />
diff --git a/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml b/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml
deleted file mode 100644
index c4e5d97..0000000
--- a/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_next_affiliated_task_bounce_scale.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0,0 c 0.8,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml b/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml
deleted file mode 100644
index 40a08b9..0000000
--- a/packages/SystemUI/legacy/recents/res/interpolator/recents_launch_prev_affiliated_task_bounce_ydelta.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0,0 c 0.6,0 0.2,1 1,1" />
diff --git a/packages/SystemUI/legacy/recents/res/interpolator/recents_to_launcher_enter_interpolator.xml b/packages/SystemUI/legacy/recents/res/interpolator/recents_to_launcher_enter_interpolator.xml
deleted file mode 100644
index c61dfd8..0000000
--- a/packages/SystemUI/legacy/recents/res/interpolator/recents_to_launcher_enter_interpolator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:controlX1="0.4"
-    android:controlY1="0"
-    android:controlX2="1"
-    android:controlY2="1" />
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents.xml b/packages/SystemUI/legacy/recents/res/layout/recents.xml
deleted file mode 100644
index ae89631..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <!-- Recents View -->
-    <com.android.systemui.recents.views.RecentsView
-        android:id="@+id/recents_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </com.android.systemui.recents.views.RecentsView>
-
-    <!-- Incompatible task overlay -->
-    <ViewStub android:id="@+id/incompatible_app_overlay_stub"
-        android:inflatedId="@+id/incompatible_app_overlay"
-        android:layout="@layout/recents_incompatible_app_overlay"
-        android:layout_width="match_parent"
-        android:layout_height="128dp"
-        android:layout_gravity="center_horizontal|top" />
-
-    <!-- Nav Bar Scrim View -->
-    <ImageView
-        android:id="@+id/nav_bar_scrim"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal|bottom"
-        android:scaleType="fitXY"
-        android:src="@drawable/recents_lower_gradient" />
-</FrameLayout>
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_empty.xml b/packages/SystemUI/legacy/recents/res/layout/recents_empty.xml
deleted file mode 100644
index d7f058c..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_empty.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:gravity="center"
-    android:drawableTop="@drawable/recents_empty"
-    android:drawablePadding="25dp"
-    android:textSize="16sp"
-    android:drawableTint="?attr/wallpaperTextColor"
-    android:textColor="?attr/wallpaperTextColor"
-    android:text="@string/recents_empty_message"
-    android:fontFamily="sans-serif"
-    android:visibility="gone" />
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_grid_task_view.xml b/packages/SystemUI/legacy/recents/res/layout/recents_grid_task_view.xml
deleted file mode 100644
index 1c9b9ac..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_grid_task_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.systemui.recents.views.grid.GridTaskView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true">
-    <com.android.systemui.recents.views.grid.GridTaskViewThumbnail
-        android:id="@+id/task_view_thumbnail"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-    <include layout="@layout/recents_task_view_header" />
-
-    <!-- TODO: Move this into a view stub -->
-    <include layout="@layout/recents_task_view_lock_to_app"/>
-
-    <!-- The incompatible app toast -->
-    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
-</com.android.systemui.recents.views.grid.GridTaskView>
-
-
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_incompatible_app_overlay.xml b/packages/SystemUI/legacy/recents/res/layout/recents_incompatible_app_overlay.xml
deleted file mode 100644
index a1c1e5b..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_incompatible_app_overlay.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:alpha="0"
-    android:background="#88000000"
-    android:forceHasOverlappingRendering="false">
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:drawableTop="@drawable/recents_info_light"
-        android:drawablePadding="8dp"
-        android:text="@string/dock_non_resizeble_failed_to_dock_text"
-        android:textColor="@android:color/white" />
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_low_ram_stack_action_button.xml b/packages/SystemUI/legacy/recents/res/layout/recents_low_ram_stack_action_button.xml
deleted file mode 100644
index dca8911..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_low_ram_stack_action_button.xml
+++ /dev/null
@@ -1,34 +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/button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:paddingStart="26dp"
-    android:paddingEnd="26dp"
-    android:paddingTop="17dp"
-    android:paddingBottom="17dp"
-    android:text="@string/recents_stack_action_button_label"
-    android:textSize="14sp"
-    android:textColor="#FFFFFF"
-    android:textAllCaps="true"
-    android:fontFamily="sans-serif-medium"
-    android:background="@drawable/recents_low_ram_stack_button_background"
-    android:visibility="invisible"
-    android:forceHasOverlappingRendering="false"
-    style="?attr/clearAllStyle" />
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_search_bar.xml b/packages/SystemUI/legacy/recents/res/layout/recents_search_bar.xml
deleted file mode 100644
index 915283e..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_search_bar.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/search_bg_transparent">
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/recents_search_bar_label"
-        android:textColor="#99ffffff"
-        android:textSize="18sp"
-        android:textAllCaps="true" />
-</FrameLayout>
-
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_stack_action_button.xml b/packages/SystemUI/legacy/recents/res/layout/recents_stack_action_button.xml
deleted file mode 100644
index 4707a8c..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_stack_action_button.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:paddingStart="14dp"
-    android:paddingEnd="14dp"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
-    android:text="@string/recents_stack_action_button_label"
-    android:textSize="14sp"
-    android:textColor="?attr/wallpaperTextColor"
-    android:textAllCaps="true"
-    android:shadowColor="#99000000"
-    android:shadowDx="0"
-    android:shadowDy="2"
-    android:shadowRadius="5"
-    android:fontFamily="sans-serif-medium"
-    android:background="@drawable/recents_stack_action_background"
-    android:visibility="invisible"
-    android:forceHasOverlappingRendering="false"
-    style="?attr/clearAllStyle" />
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view.xml
deleted file mode 100644
index 015e4a2..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.systemui.recents.views.TaskView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusable="true">
-    <com.android.systemui.recents.views.TaskViewThumbnail
-        android:id="@+id/task_view_thumbnail"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-    <include layout="@layout/recents_task_view_header" />
-
-    <!-- TODO: Move this into a view stub -->
-    <include layout="@layout/recents_task_view_lock_to_app"/>
-
-    <!-- The incompatible app toast -->
-    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
-</com.android.systemui.recents.views.TaskView>
-
-
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header.xml
deleted file mode 100644
index 1734506..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- The layouts params are calculated in TaskViewHeader.java -->
-<com.android.systemui.recents.views.TaskViewHeader
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/task_view_bar"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="top|center_horizontal">
-    <com.android.systemui.recents.views.FixedSizeImageView
-        android:id="@+id/icon"
-        android:contentDescription="@string/recents_app_info_button_label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|start"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
-        android:paddingStart="16dp"
-        android:paddingEnd="12dp" />
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|start"
-        android:textSize="16sp"
-        android:textColor="#ffffffff"
-        android:text="@string/recents_empty_message"
-        android:fontFamily="sans-serif-medium"
-        android:singleLine="true"
-        android:maxLines="1"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal"
-        android:forceHasOverlappingRendering="false" />
-    <com.android.systemui.recents.views.FixedSizeImageView
-        android:id="@+id/move_task"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|end"
-        android:padding="@dimen/recents_task_view_header_button_padding"
-        android:src="@drawable/star"
-        android:background="?android:selectableItemBackground"
-        android:alpha="0"
-        android:visibility="gone" />
-    <com.android.systemui.recents.views.FixedSizeImageView
-        android:id="@+id/dismiss_task"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|end"
-        android:padding="@dimen/recents_task_view_header_button_padding"
-        android:src="@drawable/recents_dismiss_light"
-        android:background="?android:selectableItemBackground"
-        android:alpha="0"
-        android:visibility="gone" />
-
-    <!-- The app overlay shows as the user long-presses on the app icon -->
-    <ViewStub android:id="@+id/app_overlay_stub"
-               android:inflatedId="@+id/app_overlay"
-               android:layout="@layout/recents_task_view_header_overlay"
-               android:layout_width="match_parent"
-               android:layout_height="match_parent" />
-</com.android.systemui.recents.views.TaskViewHeader>
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_overlay.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_overlay.xml
deleted file mode 100644
index cf09b1d..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_overlay.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<!-- The layouts params are calculated in TaskViewHeader.java -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <com.android.systemui.recents.views.FixedSizeImageView
-        android:id="@+id/app_icon"
-        android:contentDescription="@string/recents_app_info_button_label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|start"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
-        android:paddingStart="16dp"
-        android:paddingEnd="12dp" />
-    <TextView
-        android:id="@+id/app_title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|start"
-        android:textSize="16sp"
-        android:textColor="#ffffffff"
-        android:text="@string/recents_empty_message"
-        android:fontFamily="sans-serif-medium"
-        android:singleLine="true"
-        android:maxLines="2"
-        android:ellipsize="marquee"
-        android:fadingEdge="horizontal" />
-    <com.android.systemui.recents.views.FixedSizeImageView
-        android:id="@+id/app_info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|end"
-        android:padding="@dimen/recents_task_view_header_button_padding"
-        android:background="?android:selectableItemBackground"
-        android:src="@drawable/recents_info_light" />
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_progress_bar.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_progress_bar.xml
deleted file mode 100644
index f352632..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_header_progress_bar.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<ProgressBar
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="?android:attr/progressBarStyleHorizontal"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:indeterminateOnly="false"
-    android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_incompatible_app_toast.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view_incompatible_app_toast.xml
deleted file mode 100644
index d573d6b..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_incompatible_app_toast.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<ViewStub
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/incompatible_app_toast_stub"
-    android:inflatedId="@+id/incompatible_app_toast"
-    android:layout="@*android:layout/transient_notification"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="top|center_horizontal"
-    android:layout_marginTop="48dp"
-    android:layout_marginLeft="16dp"
-    android:layout_marginRight="16dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_lock_to_app.xml b/packages/SystemUI/legacy/recents/res/layout/recents_task_view_lock_to_app.xml
deleted file mode 100644
index 8cece11..0000000
--- a/packages/SystemUI/legacy/recents/res/layout/recents_task_view_lock_to_app.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/lock_to_app_fab"
-    android:layout_width="@dimen/recents_lock_to_app_size"
-    android:layout_height="@dimen/recents_lock_to_app_size"
-    android:layout_gravity="bottom|end"
-    android:layout_marginEnd="15dp"
-    android:layout_marginBottom="15dp"
-    android:translationZ="4dp"
-    android:contentDescription="@string/recents_lock_to_app_button_label"
-    android:background="@drawable/recents_lock_to_task_button_bg"
-    android:visibility="invisible"
-    android:alpha="0">
-    <ImageView
-        android:layout_width="@dimen/recents_lock_to_app_icon_size"
-        android:layout_height="@dimen/recents_lock_to_app_icon_size"
-        android:layout_gravity="center"
-        android:src="@drawable/recents_lock_to_app_pin" />
-</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values-af/strings.xml b/packages/SystemUI/legacy/recents/res/values-af/strings.xml
deleted file mode 100644
index 736c810..0000000
--- a/packages/SystemUI/legacy/recents/res/values-af/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Oorsig."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Maak <xliff:g id="APP">%s</xliff:g> toe."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> is toegemaak."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alle onlangse programme is toegemaak."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Maak <xliff:g id="APP">%s</xliff:g>-programinligting oop."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Begin tans <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Geen onlangse items nie"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Jy het alles toegemaak"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Programinligting"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"skermvaspen"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"soek"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Kon nie <xliff:g id="APP">%s</xliff:g> begin nie."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is in veiligmodus gedeaktiveer."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Vee alles uit"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Sleep hierheen om verdeelde skerm te gebruik"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Verdeel horisontaal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Verdeel vertikaal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Verdeel gepasmaak"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Verdeel skerm na bo"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Verdeel skerm na links"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Verdeel skerm na regs"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-am/strings.xml b/packages/SystemUI/legacy/recents/res/values-am/strings.xml
deleted file mode 100644
index 2870be7..0000000
--- a/packages/SystemUI/legacy/recents/res/values-am/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"አጠቃላይ እይታ።"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> አስወግድ።"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ተሰናብቷል።"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ሁሉም የቅርብ ጊዜ ማመልከቻዎች ተሰናብተዋል።"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"የ<xliff:g id="APP">%s</xliff:g> መተግበሪያ መረጃውን ይክፈቱ።"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> በመጀመር ላይ።"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ሁሉንም ነገር አጽድተዋል"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"የመተግበሪያ መረጃ"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ማያ ገጽ መሰካት"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ፈልግ"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>ን መጀመር አልተቻለም።"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> በጥንቃቄ ሁነታ ውስጥ ታግዷል።"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ሁሉንም አጽዳ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"የተከፈለ ማያ ገጽን ለመጠቀም እዚህ ላይ ይጎትቱ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"አግድም ክፈል"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ቁልቁል ክፈል"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"በብጁ ክፈል"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ማያ ገጽ ወደ ላይ ክፈል"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ማያ ገጽ ወደ ግራ ክፈል"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ማያ ገጽ ወደ ቀኝ ክፈል"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ar/strings.xml b/packages/SystemUI/legacy/recents/res/values-ar/strings.xml
deleted file mode 100644
index 004de41..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ar/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"النظرة عامة"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"إزالة <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"تمَّت إزالة <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"تمَّت إزالة كل التطبيقات المستخدمة مؤخرًا."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"فتح معلومات تطبيق <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"جارٍ بدء <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ليست هناك عناصر تم استخدامها مؤخرًا"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"لقد محوتَ كل شيء"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"معلومات التطبيق"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"تثبيت الشاشة"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"بحث"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"تعذَّر بدء <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"تم إيقاف <xliff:g id="APP">%s</xliff:g> في الوضع الآمن."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"محو الكل"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"اسحب هنا لاستخدام وضع تقسيم الشاشة"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"تقسيم أفقي"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"تقسيم رأسي"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"تقسيم مخصَّص"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"تقسيم الشاشة بمحاذاة الجزء العلوي"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"تقسيم الشاشة بمحاذاة اليسار"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"تقسيم الشاشة بمحاذاة اليمين"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-as/strings.xml b/packages/SystemUI/legacy/recents/res/values-as/strings.xml
deleted file mode 100644
index c742dab..0000000
--- a/packages/SystemUI/legacy/recents/res/values-as/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"অৱলোকন।"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"শেহতীয়া-ৰ তালিকাৰ পৰা <xliff:g id="APP">%s</xliff:g>ক আঁতৰাওক।"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"শেহতীয়া-ৰ তালিকাৰ পৰা <xliff:g id="APP">%s</xliff:g>ক আঁতৰোৱা হ’ল।"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"শেহতীয়া-ৰ তালিকাৰ পৰা সকলো এপ্লিকেশ্বন আঁতৰোৱা হ’ল।"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> এপ্লিকেশ্বনৰ তথ্য খোলক।"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>ক আৰম্ভ কৰা হৈছে।"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"কোনো শেহতীয়া বস্তু নাই"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"আপুনি সকলো খালী কৰিলে"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"এপ্লিকেশ্বনৰ তথ্য"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"স্ক্ৰীণ পিনিং"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"সন্ধান কৰক"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>ক আৰম্ভ কৰিব পৰা নগ’ল।"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>টো সুৰক্ষিত ম’ডত অক্ষম কৰা হ’ল।"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"সকলো মচক"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"বিভাজিত স্ক্ৰীণ ব্যৱহাৰ কৰিবলৈ ইয়ালৈ টানি আনি এৰক"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"আনুভূমিকভাৱে বিভাজন কৰক"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"উলম্বভাৱে বিভাজন কৰক"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"কাষ্টম বিভাজন কৰক"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"স্ক্ৰীণখনক ওপৰফাললৈ ভাগ কৰক"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"স্ক্ৰীণখনক বাওঁফাললৈ ভাগ কৰক"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"স্ক্ৰীণখনক সোঁফাললৈ ভাগ কৰক"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-az/strings.xml b/packages/SystemUI/legacy/recents/res/values-az/strings.xml
deleted file mode 100644
index 76ae02a..0000000
--- a/packages/SystemUI/legacy/recents/res/values-az/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"İcmal."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> tətbiqini silin."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> silindi."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Bütün son tətbiqlər silindi."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> tətbiq məlumatını açın."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> başladılır."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ən son element yoxdur"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Hər şeyi sildiniz"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Tətbiq məlumatı"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ekran sancağı"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"axtarış"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> başladılmadı."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> təhlükəsiz rejimdə deaktiv edildi."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Hamısını silin"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Bölünmüş ekrandan istifadə etmək üçün bura sürüşdürün"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horizontal Bölün"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikal Bölün"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Fərdi Bölün"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Ekranı yuxarıya doğru bölün"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Ekranı sola doğru bölün"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Ekranı sağa doğru bölün"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/legacy/recents/res/values-b+sr+Latn/strings.xml
deleted file mode 100644
index 3117eea..0000000
--- a/packages/SystemUI/legacy/recents/res/values-b+sr+Latn/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Pregled."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Odbacite aplikaciju <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikacija <xliff:g id="APP">%s</xliff:g> je odbačena."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Sve nedavno korišćene aplikacije su odbačene."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otvorite informacije o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Pokreće se <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nema nedavnih stavki"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Obrisali ste sve"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informacije o aplikaciji"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"kačenje ekrana"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pretraži"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Pokretanje aplikacije <xliff:g id="APP">%s</xliff:g> nije uspelo."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikacija <xliff:g id="APP">%s</xliff:g> je onemogućena u bezbednom režimu."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Obriši sve"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Prevucite ovde da biste koristili razdeljeni ekran"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Podeli horizontalno"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Podeli vertikalno"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Podeli prilagođeno"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Podeli ekran nagore"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Podeli ekran nalevo"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Podeli ekran nadesno"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-be/strings.xml b/packages/SystemUI/legacy/recents/res/values-be/strings.xml
deleted file mode 100644
index 8121846..0000000
--- a/packages/SystemUI/legacy/recents/res/values-be/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Агляд."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Закрыць праграму \"<xliff:g id="APP">%s</xliff:g>\"."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Праграма \"<xliff:g id="APP">%s</xliff:g>\" закрыта."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Усе нядаўнія праграмы закрыты."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Адкрыць інфармацыю пра праграму \"<xliff:g id="APP">%s</xliff:g>\"."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Запускаецца праграма \"<xliff:g id="APP">%s</xliff:g>\"."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Няма нядаўніх элементаў"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Вы ўсё выдалілі"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Інфармацыя пра праграму"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"замацаванне экрана"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"пошук"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Не ўдалося запусціць праграму \"<xliff:g id="APP">%s</xliff:g>\"."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Праграма \"<xliff:g id="APP">%s</xliff:g>\" адключана ў бяспечным рэжыме."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ачысціць усё"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Перацягніце сюды, каб перайсці ў рэжым падзеленага экрана"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Падзяліць гарызантальна"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Падзяліць вертыкальна"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Падзяліць іншым чынам"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Падзяліць экран зверху"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Падзяліць экран злева"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Падзяліць экран справа"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-bg/strings.xml b/packages/SystemUI/legacy/recents/res/values-bg/strings.xml
deleted file mode 100644
index 3dda34f..0000000
--- a/packages/SystemUI/legacy/recents/res/values-bg/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Общ преглед."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Отхвърляне на <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Приложението <xliff:g id="APP">%s</xliff:g> е отхвърлено."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Всички скорошни приложения са отхвърлени."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Отворете информацията за приложението <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> се стартира."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Няма скорошни елементи"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Изчистихте всичко"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Информация за приложението"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"фиксиране на екрана"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"търсене"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> не можа да стартира."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Приложението <xliff:g id="APP">%s</xliff:g> е деактивирано в безопасния режим."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Изчистване на всичко"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Преместете тук с плъзгане, за да използвате режим за разделен екран"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Хоризонтално разделяне"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Вертикално разделяне"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Персонализирано разделяне"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Разделяне на екрана нагоре"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Разделяне на екрана наляво"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Разделяне на екрана надясно"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-bn/strings.xml b/packages/SystemUI/legacy/recents/res/values-bn/strings.xml
deleted file mode 100644
index b22672e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-bn/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"এক নজরে।"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> খারিজ করুন।"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> খারিজ করা হয়েছে।"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"সব সাম্প্রতিক অ্যাপ্লিকেশন খারিজ করা হয়েছে।"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> অ্যাপ্লিকেশনের তথ্য খুলুন।"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> শুরু করা হচ্ছে।"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"কোনো সাম্প্রতিক আইটেম নেই"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"আপনি সবকিছু মুছে দিয়েছেন"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"অ্যাপ্লিকেশনের তথ্য"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"স্ক্রিন পিন করা"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"খুঁজুন"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> চালু করা যায়নি।"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"নিরাপদ মোডে <xliff:g id="APP">%s</xliff:g> বন্ধ করা আছে।"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"সবগুলি মুছে দিন"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"স্প্লিট স্ক্রিন ব্যবহার করতে এখানে টেনে আনুন"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"অনুভূমিক স্প্লিট করুন"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"উল্লম্ব স্প্লিট করুন"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"কাস্টম স্প্লিট করুন"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"স্ক্রিনটি উপরের দিকে স্প্লিট করুন"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"স্ক্রিনটি বাঁদিকে স্প্লিট করুন"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"স্ক্রিনটি ডানদিকে স্প্লিট করুন"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-bs/strings.xml b/packages/SystemUI/legacy/recents/res/values-bs/strings.xml
deleted file mode 100644
index 8e149ba8..0000000
--- a/packages/SystemUI/legacy/recents/res/values-bs/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Pregled."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Odbaci aplikaciju <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikacija <xliff:g id="APP">%s</xliff:g> je odbačena."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Sve nedavno korištene aplikacije su odbačene."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otvaranje informacija o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Pokretanje aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nema nedavnih stavki"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Sve ste obrisali"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informacije o aplikaciji"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"kačenje ekrana"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pretraži"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Aplikacija <xliff:g id="APP">%s</xliff:g> nije pokrenuta."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikacija <xliff:g id="APP">%s</xliff:g> je onemogućena u sigurnom načinu rada."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Obriši sve"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Povucite ovdje za korištenje podijeljenog ekrana"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Podjela po horizontali"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Podjela po vertikali"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Prilagođena podjela"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dijeli ekran nagore"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dijeli ekran nalijevo"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dijeli ekran nadesno"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ca/strings.xml b/packages/SystemUI/legacy/recents/res/values-ca/strings.xml
deleted file mode 100644
index fff525c..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ca/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Aplicacions recents."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Ignora <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"S\'ha ignorat <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"S\'han ignorat totes les aplicacions recents."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Obre la informació sobre l\'aplicació <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"S\'està iniciant <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No hi ha cap element recent"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Ho has esborrat tot"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informació de l\'aplicació"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fixació de pantalla"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"cerca"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"No s\'ha pogut iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"En mode segur, l\'aplicació <xliff:g id="APP">%s</xliff:g> està desactivada."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Esborra-ho tot"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arrossega-ho aquí per utilitzar la pantalla dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Divisió horitzontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Divisió vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Divisió personalitzada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Divideix la pantalla cap amunt"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Divideix la pantalla cap a l\'esquerra"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Divideix la pantalla cap a la dreta"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-cs/strings.xml b/packages/SystemUI/legacy/recents/res/values-cs/strings.xml
deleted file mode 100644
index 200f7a8..0000000
--- a/packages/SystemUI/legacy/recents/res/values-cs/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Přehled"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Zavřít aplikaci <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikace <xliff:g id="APP">%s</xliff:g> byla odebrána."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Všechny naposledy použité aplikace byly odstraněny."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otevře informace o aplikaci <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Spouštění aplikace <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Žádné nedávné položky"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Vše je vymazáno"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informace o aplikaci"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"připnutí obrazovky"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"hledat"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Aplikaci <xliff:g id="APP">%s</xliff:g> nelze spustit."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikace <xliff:g id="APP">%s</xliff:g> je v nouzovém režimu zakázána."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Vymazat vše"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Přetáhnutím sem aktivujete rozdělenou obrazovku"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Vodorovné rozdělení"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Svislé rozdělení"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Vlastní rozdělení"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Rozdělit obrazovku nahoru"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Rozdělit obrazovku vlevo"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Rozdělit obrazovku vpravo"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-da/strings.xml b/packages/SystemUI/legacy/recents/res/values-da/strings.xml
deleted file mode 100644
index 0a1690e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-da/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Oversigt."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Fjern <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> er fjernet."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alle de seneste apps er fjernet."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Åbn appoplysningerne for <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> åbnes."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ingen nye elementer"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Du har ryddet alt"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Appoplysninger"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"skærmfastholdelse"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"søg"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> kunne ikke åbnes."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> er deaktiveret i sikker tilstand."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ryd alle"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Træk hertil for at bruge opdelt skærm"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Opdel vandret"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Opdel lodret"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Opdel brugerdefineret"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Opdelt skærm øverst"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Opdelt skærm til venstre"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Opdelt skærm til højre"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-de/strings.xml b/packages/SystemUI/legacy/recents/res/values-de/strings.xml
deleted file mode 100644
index 4a089bf..0000000
--- a/packages/SystemUI/legacy/recents/res/values-de/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Übersicht."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> entfernen."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> wurde entfernt."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alle kürzlich verwendeten Apps wurden entfernt."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Infos zur <xliff:g id="APP">%s</xliff:g> App öffnen."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> wird gestartet."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Keine kürzlich verwendeten Elemente"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Du hast alles gelöscht"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"App-Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Bildschirm anpinnen"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"Suchen"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> konnte nicht gestartet werden."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ist im abgesicherten Modus deaktiviert."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Alle löschen"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Hierher ziehen, um den Bildschirm zu teilen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Geteilt – horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Geteilt – vertikal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Geteilt – benutzerdefiniert"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Geteilten Bildschirm oben anzeigen"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Geteilten Bildschirm links anzeigen"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Geteilten Bildschirm rechts anzeigen"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-el/strings.xml b/packages/SystemUI/legacy/recents/res/values-el/strings.xml
deleted file mode 100644
index 90baf52..0000000
--- a/packages/SystemUI/legacy/recents/res/values-el/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Επισκόπηση."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Παράβλεψη εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Η εφαρμογή <xliff:g id="APP">%s</xliff:g> απορρίφθηκε."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Όλες οι πρόσφατες εφαρμογές παραβλέφθηκαν."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Άνοιγμα πληροφοριών εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Έναρξη εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Διαγράψατε όλα τα στοιχεία"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Πληροφορίες εφαρμογής"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"καρφίτσωμα οθόνης"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"αναζήτηση"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Δεν ήταν δυνατή η έναρξη της εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Η εφαρμογή <xliff:g id="APP">%s</xliff:g> έχει απενεργοποιηθεί στην ασφαλή λειτουργία."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Διαγραφή όλων"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Σύρετε εδώ για να χρησιμοποιήσετε τον διαχωρισμό οθόνης"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Οριζόντιος διαχωρισμός"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Κάθετος διαχωρισμός"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Προσαρμοσμένος διαχωρισμός"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Διαχωρισμός οθόνης στην κορυφή"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Διαχωρισμός οθόνης στα αριστερά"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Διαχωρισμός οθόνης στα δεξιά"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-en-rAU/strings.xml b/packages/SystemUI/legacy/recents/res/values-en-rAU/strings.xml
deleted file mode 100644
index af1d055..0000000
--- a/packages/SystemUI/legacy/recents/res/values-en-rAU/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overview."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dismiss <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dismissed."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"All recent applications dismissed."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Open <xliff:g id="APP">%s</xliff:g> application info."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Starting <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No recent items"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"You\'ve cleared everything"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Application Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"screen pinning"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"search"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is disabled in safe-mode."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Clear all"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Drag here to use split screen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Split Horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Split Vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Split Custom"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Split screen to the top"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Split screen to the left"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Split screen to the right"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-en-rCA/strings.xml b/packages/SystemUI/legacy/recents/res/values-en-rCA/strings.xml
deleted file mode 100644
index af1d055..0000000
--- a/packages/SystemUI/legacy/recents/res/values-en-rCA/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overview."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dismiss <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dismissed."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"All recent applications dismissed."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Open <xliff:g id="APP">%s</xliff:g> application info."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Starting <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No recent items"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"You\'ve cleared everything"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Application Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"screen pinning"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"search"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is disabled in safe-mode."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Clear all"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Drag here to use split screen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Split Horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Split Vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Split Custom"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Split screen to the top"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Split screen to the left"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Split screen to the right"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-en-rGB/strings.xml b/packages/SystemUI/legacy/recents/res/values-en-rGB/strings.xml
deleted file mode 100644
index af1d055..0000000
--- a/packages/SystemUI/legacy/recents/res/values-en-rGB/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overview."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dismiss <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dismissed."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"All recent applications dismissed."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Open <xliff:g id="APP">%s</xliff:g> application info."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Starting <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No recent items"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"You\'ve cleared everything"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Application Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"screen pinning"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"search"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is disabled in safe-mode."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Clear all"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Drag here to use split screen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Split Horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Split Vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Split Custom"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Split screen to the top"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Split screen to the left"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Split screen to the right"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-en-rIN/strings.xml b/packages/SystemUI/legacy/recents/res/values-en-rIN/strings.xml
deleted file mode 100644
index af1d055..0000000
--- a/packages/SystemUI/legacy/recents/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overview."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dismiss <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dismissed."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"All recent applications dismissed."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Open <xliff:g id="APP">%s</xliff:g> application info."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Starting <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No recent items"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"You\'ve cleared everything"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Application Info"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"screen pinning"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"search"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is disabled in safe-mode."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Clear all"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Drag here to use split screen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Split Horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Split Vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Split Custom"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Split screen to the top"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Split screen to the left"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Split screen to the right"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-en-rXC/strings.xml b/packages/SystemUI/legacy/recents/res/values-en-rXC/strings.xml
deleted file mode 100644
index ceb6b13..0000000
--- a/packages/SystemUI/legacy/recents/res/values-en-rXC/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎Overview.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‎‎Dismiss ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎ dismissed.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‎All recent applications dismissed.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎Open ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎ application info.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎Starting ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎No recent items‎‏‎‎‏‎"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎You\'ve cleared everything‎‏‎‎‏‎"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎Application Info‎‏‎‎‏‎"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‎‏‎‏‎screen pinning‎‏‎‎‏‎"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‏‎search‎‏‎‎‏‎"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‎Could not start ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎ is disabled in safe-mode.‎‏‎‎‏‎"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎Clear all‎‏‎‎‏‎"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎Drag here to use split screen‎‏‎‎‏‎"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎Split Horizontal‎‏‎‎‏‎"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‏‏‎‏‎Split Vertical‎‏‎‎‏‎"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‏‏‎Split Custom‎‏‎‎‏‎"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‎Split screen to the top‎‏‎‎‏‎"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎Split screen to the left‎‏‎‎‏‎"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎Split screen to the right‎‏‎‎‏‎"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-es-rUS/strings.xml b/packages/SystemUI/legacy/recents/res/values-es-rUS/strings.xml
deleted file mode 100644
index f212b02..0000000
--- a/packages/SystemUI/legacy/recents/res/values-es-rUS/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Recientes"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Permite descartar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Se descartó <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Se descartaron todas las aplicaciones recientes."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Permite abrir la información de la aplicación de <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iniciando <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No hay elementos recientes"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Todo borrado"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Información de la aplicación"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Fijar pantalla"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"Buscar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"No se pudo iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> está inhabilitada en modo seguro."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Borrar todo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arrastra hasta aquí para usar la pantalla dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"División horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"División vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"División personalizada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dividir pantalla en la parte superior"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dividir pantalla a la izquierda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dividir pantalla a la derecha"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-es/strings.xml b/packages/SystemUI/legacy/recents/res/values-es/strings.xml
deleted file mode 100644
index 8bcfe84..0000000
--- a/packages/SystemUI/legacy/recents/res/values-es/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Aplicaciones recientes."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Cerrar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Se ha ignorado la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Se han ignorado todas las aplicaciones recientes."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Abre la información de la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iniciando la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"No hay elementos recientes"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Has borrado todo"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Información de la aplicación"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fijar pantalla"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"buscar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"No se ha podido iniciar la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"La aplicación <xliff:g id="APP">%s</xliff:g> se ha inhabilitado en modo seguro."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Borrar todo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arrastra el elemento hasta aquí para utilizar la pantalla dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"División horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"División vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"División personalizada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dividir la pantalla en la parte superior"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dividir la pantalla a la izquierda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dividir la pantalla a la derecha"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-et/strings.xml b/packages/SystemUI/legacy/recents/res/values-et/strings.xml
deleted file mode 100644
index c1903af..0000000
--- a/packages/SystemUI/legacy/recents/res/values-et/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Ülevaade."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Rakendusest <xliff:g id="APP">%s</xliff:g> loobumine."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Rakendusest <xliff:g id="APP">%s</xliff:g> on loobutud."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Kõikidest hiljutistest rakendustest on loobutud."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Rakenduse <xliff:g id="APP">%s</xliff:g> teabe avamine."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Rakenduse <xliff:g id="APP">%s</xliff:g> käivitamine."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Hiljutisi üksusi pole"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Olete kõik ära kustutanud"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Rakenduse teave"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ekraanikuva kinnitamine"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"otsi"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Rakendust <xliff:g id="APP">%s</xliff:g> ei saanud käivitada."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Rakendus <xliff:g id="APP">%s</xliff:g> on turvarežiimis keelatud."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Kustuta kõik"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Jagatud ekraani kasutamiseks lohistage siia"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horisontaalne poolitamine"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikaalne poolitamine"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Kohandatud poolitamine"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Poolita ekraan üles"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Poolita ekraan vasakule"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Poolita ekraan paremale"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-eu/strings.xml b/packages/SystemUI/legacy/recents/res/values-eu/strings.xml
deleted file mode 100644
index 91e250f..0000000
--- a/packages/SystemUI/legacy/recents/res/values-eu/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Ikuspegi orokorra."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Baztertu <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Baztertu da <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Baztertu dira azken aplikazio guztiak."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Ireki <xliff:g id="APP">%s</xliff:g> aplikazioari buruzko informazioa."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> abiarazten."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ez dago azkenaldi honetako ezer"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Dena garbitu duzu"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Aplikazioaren informazioa"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"pantaila-ainguratzea"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"bilatu"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Ezin izan da abiarazi <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> desgaituta dago modu seguruan."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Garbitu guztiak"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arrastatu hona pantaila zatitzeko"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Zatitze horizontala"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Zatitze bertikala"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Zatitze pertsonalizatua"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Zatitu pantaila eta ezarri goian"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Zatitu pantaila eta ezarri ezkerrean"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Zatitu pantaila eta ezarri eskuinean"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-fa/strings.xml b/packages/SystemUI/legacy/recents/res/values-fa/strings.xml
deleted file mode 100644
index 61e87c1..0000000
--- a/packages/SystemUI/legacy/recents/res/values-fa/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"نمای کلی."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"رد کردن <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> نادیده گرفته شد."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"همه برنامه‌های اخیر رد شدند."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"باز کردن اطلاعات برنامه <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> درحال شروع به کار است."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"بدون موارد اخیر"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"همه‌چیز را پاک کرده‌اید"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"اطلاعات برنامه"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"پین کردن صفحه"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"جستجو"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> شروع نشد."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> در حالت ایمن غیرفعال است."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"پاک کردن همه"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"برای استفاده از تقسیم صفحه، به اینجا بکشید"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"تقسیم افقی"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"تقسیم عمودی"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"سفارشی کردن تقسیم"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"تقسیم کردن صفحه به بالا"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"تقسیم کردن صفحه به چپ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"تقسیم کردن صفحه به راست"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-fi/strings.xml b/packages/SystemUI/legacy/recents/res/values-fi/strings.xml
deleted file mode 100644
index bf2e461..0000000
--- a/packages/SystemUI/legacy/recents/res/values-fi/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Viimeisimmät"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Hylkää <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> hylättiin."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Kaikki viimeisimmät sovellukset on hylätty."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Avaa sovelluksen <xliff:g id="APP">%s</xliff:g> tiedot."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Käynnistetään <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ei viimeaikaisia kohteita"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Kaikki on hoidettu"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Sovellustiedot"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"näytön kiinnitys"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"haku"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ei käynnistynyt."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> on poistettu käytöstä vikasietotilassa."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Poista kaikki"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Jaa näyttö vetämällä tähän."</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Vaakasuuntainen jako"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Pystysuuntainen jako"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Oma jako"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Jaa näyttö ylös"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Jaa näyttö vasemmalle"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Jaa näyttö oikealle"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-fr-rCA/strings.xml b/packages/SystemUI/legacy/recents/res/values-fr-rCA/strings.xml
deleted file mode 100644
index e60727e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Aperçu"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Supprimer <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> supprimée."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Toutes les applications récentes ont été supprimées."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Ouvre les détails de l\'application <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Lancement de <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Aucun élément récent"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Vous avez tout effacé"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Détails de l\'application"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"épinglage d\'écran"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"rechercher"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> est désactivée en mode sans échec."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Tout effacer"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Glissez l\'élément ici pour utiliser l\'écran partagé"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Séparation horizontale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Séparation verticale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Séparation personnalisée"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Écran partagé dans le haut"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Écran partagé à la gauche"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Écran partagé à la droite"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-fr/strings.xml b/packages/SystemUI/legacy/recents/res/values-fr/strings.xml
deleted file mode 100644
index 5b0d611..0000000
--- a/packages/SystemUI/legacy/recents/res/values-fr/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Aperçu"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Supprimer l\'application <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Application <xliff:g id="APP">%s</xliff:g> supprimée."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Toutes les applications récentes ont été supprimées."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Ouvre les informations sur l\'application <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Lancement de l\'application <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Aucun élément récent"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Vous avez tout effacé"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informations sur l\'application"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"épinglage d\'écran"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"rechercher"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Impossible de lancer l\'application <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"L\'application <xliff:g id="APP">%s</xliff:g> est désactivée en mode sécurisé."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Tout fermer"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Faire glisser ici pour utiliser l\'écran partagé"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Séparation horizontale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Séparation verticale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Séparation personnalisée"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Partager l\'écran en haut"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Partager l\'écran sur la gauche"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Partager l\'écran sur la droite"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-gl/strings.xml b/packages/SystemUI/legacy/recents/res/values-gl/strings.xml
deleted file mode 100644
index 008c776..0000000
--- a/packages/SystemUI/legacy/recents/res/values-gl/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Visión xeral."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Rexeita <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Rexeitouse <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Rexeitáronse todas as aplicacións recentes."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Abre a información da aplicación <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Non hai elementos recentes"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Borraches todo"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Información da aplicación"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fixación de pantalla"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"buscar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Non se puido iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"A aplicación <xliff:g id="APP">%s</xliff:g> está desactivada no modo seguro."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Borrar todo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arrastra aquí para usar a pantalla dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Dividir horizontalmente"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Dividir verticalmente"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Dividir de xeito personalizado"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dividir pantalla arriba"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dividir pantalla á esquerda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dividir pantalla á dereita"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-gu/strings.xml b/packages/SystemUI/legacy/recents/res/values-gu/strings.xml
deleted file mode 100644
index 33dc7e8..0000000
--- a/packages/SystemUI/legacy/recents/res/values-gu/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ઝલક."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> કાઢી નાખો."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> કાઢી નાખી."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"તાજેતરની બધી ઍપ્લિકેશનો કાઢી નાખવામાં આવી."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g>ની ઍપ્લિકેશન માહિતી ખોલો."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>ને શરૂ કરી રહ્યાં છીએ."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"તાજેતરની કોઈ આઇટમ નથી"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"તમે બધું સાફ કર્યું"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ઍપ્લિકેશનની માહિતી"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"સ્ક્રીન પિનિંગ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"શોધો"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>ને શરૂ કરી શકાઈ નથી."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"સુરક્ષિત મોડમાં <xliff:g id="APP">%s</xliff:g>ને બંધ કરવામાં આવી છે."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"બધું સાફ કરો"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"વિભાજિત સ્ક્રીનનો ઉપયોગ કરવા માટે અહીં ખેંચો"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"સ્ક્રીનને આડી વિભાજિત કરો"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"સ્ક્રીનને ઊભી વિભાજિત કરો"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"સ્ક્રીનને કસ્ટમ વિભાજિત કરો"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"સ્ક્રીનને ઉપરની તરફ વિભાજિત કરો"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"સ્ક્રીનને ડાબી તરફ વિભાજિત કરો"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"સ્ક્રીનને જમણી તરફ વિભાજિત કરો"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-hi/strings.xml b/packages/SystemUI/legacy/recents/res/values-hi/strings.xml
deleted file mode 100644
index 3f19f33..0000000
--- a/packages/SystemUI/legacy/recents/res/values-hi/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"खास जानकारी."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> को खारिज करें."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> खारिज किया गया."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"हाल के सभी ऐप्लिकेशन खारिज कर दिए गए हैं."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ऐप्लिकेशन की जानकारी खोलें."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> शुरू किया जा रहा है."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"हाल का कोई आइटम नहीं है"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"आपने सब कुछ हटा दिया है"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ऐप्लिकेशन की जानकारी"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"स्क्रीन पिन करना"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"खोजें"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> शुरू नहीं किया जा सका."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> को सुरक्षित-मोड में बंद किया गया."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"सभी ऐप्लिकेशन बंद करें"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"स्क्रीन को दो हिस्सों में बाँटने (स्प्लिट स्क्रीन) के लिए यहां से खींचें और छोड़ें"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"क्षैतिज रूप से दो हिस्सों में बाँटें (स्प्लिट करें)"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"लम्बवत रूप से दो हिस्सों में बाँटें (स्प्लिट करें)"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"अपने मुताबिक दो हिस्सों में बाँटें (स्प्लिट स्क्रीन करें)"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ऊपर की ओर दूसरी स्क्रीन बनाएं"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"बाईं ओर दूसरी स्क्रीन बनाएं"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"दाईं ओर दूसरी स्क्रीन बनाएं"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-hr/strings.xml b/packages/SystemUI/legacy/recents/res/values-hr/strings.xml
deleted file mode 100644
index 88926a4..0000000
--- a/packages/SystemUI/legacy/recents/res/values-hr/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Pregled."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Odbacivanje aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Odbačena je aplikacija <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Odbačene su sve nedavne aplikacije."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otvaranje informacija o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Pokreće se aplikacija <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nema nedavnih stavki"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Izbrisali ste sve"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informacije o aplikaciji"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"prikačivanje zaslona"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pretraživanje"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Aplikacija <xliff:g id="APP">%s</xliff:g> nije pokrenuta."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikacija <xliff:g id="APP">%s</xliff:g> onemogućena je u sigurnom načinu."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Izbriši sve"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Povucite ovdje da biste upotrebljavali podijeljeni zaslon"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Podijeli vodoravno"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Podijeli okomito"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Podijeli prilagođeno"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Podijeli zaslon na vrhu"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Podijeli zaslon slijeva"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Podijeli zaslon zdesna"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-hu/strings.xml b/packages/SystemUI/legacy/recents/res/values-hu/strings.xml
deleted file mode 100644
index d0429e7..0000000
--- a/packages/SystemUI/legacy/recents/res/values-hu/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Áttekintés."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"A(z) <xliff:g id="APP">%s</xliff:g> elvetése."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> eltávolítva."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Az összes alkalmazás eltávolítva a nemrég használtak közül."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"A(z) <xliff:g id="APP">%s</xliff:g> alkalmazás adatainak megnyitása."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"A(z) <xliff:g id="APP">%s</xliff:g> indítása."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nincsenek mostanában használt elemek"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Mindent törölt"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Az alkalmazás adatai"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"képernyő rögzítése"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"keresés"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Nem lehet elindítani a következőt: <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"A(z) <xliff:g id="APP">%s</xliff:g> csökkentett módban le van tiltva."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Összes törlése"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Húzza ide az osztott képernyő használatához"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Osztott vízszintes"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Osztott függőleges"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Osztott egyéni"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Osztott képernyő felülre"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Osztott képernyő balra"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Osztott képernyő jobbra"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-hy/strings.xml b/packages/SystemUI/legacy/recents/res/values-hy/strings.xml
deleted file mode 100644
index c56b691..0000000
--- a/packages/SystemUI/legacy/recents/res/values-hy/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Համատեսք:"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Հեռացնել <xliff:g id="APP">%s</xliff:g> հավելվածը ցուցակից:"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> հավելվածը հեռացվել է ցուցակից:"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Բոլոր վերջին հավելվածները հեռացվել են ցուցակից:"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Բացել <xliff:g id="APP">%s</xliff:g> հավելվածի մասին տեղեկությունները"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> հավելվածը գործարկվում է:"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Այստեղ դեռ ոչինչ չկա"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Ցուցակը դատարկ է"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Հավելվածի մասին"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"էկրանի ամրացում"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"որոնում"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Չհաջողվեց գործարկել <xliff:g id="APP">%s</xliff:g> հավելվածը:"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> հավելվածը անվտանգ ռեժիմում անջատված է:"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ջնջել բոլորը"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Քաշեք այստեղ՝ էկրանի տրոհումն օգտագործելու համար"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Հորիզոնական տրոհում"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Ուղղահայաց տրոհում"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Հատուկ տրոհում"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Տրոհել էկրանը վերևից"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Տրոհել էկրանը ձախից"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Տրոհել էկրանն աջից"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-in/strings.xml b/packages/SystemUI/legacy/recents/res/values-in/strings.xml
deleted file mode 100644
index aa9dcfe..0000000
--- a/packages/SystemUI/legacy/recents/res/values-in/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Ringkasan."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Hapus <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dihapus."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Semua aplikasi yang baru dibuka telah dihapus."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Buka info aplikasi <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Memulai <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Tidak ada item yang baru dibuka"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Anda sudah menghapus semua"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Info Aplikasi"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"pin ke layar"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"telusuri"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Tidak dapat memulai <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> dinonaktifkan dalam mode aman."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Hapus semua"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Tarik ke sini untuk menggunakan layar terpisah"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Pisahkan Horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Pisahkan Vertikal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Pisahkan Khusus"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Pisahkan layar ke atas"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Pisahkan layar ke kiri"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Pisahkan layar ke kanan"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-is/strings.xml b/packages/SystemUI/legacy/recents/res/values-is/strings.xml
deleted file mode 100644
index e0a555e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-is/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Yfirlit."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Fjarlægja <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> fjarlægt."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Öll nýleg forrit fjarlægð."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Opna forritsupplýsingar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Ræsir <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Engin nýleg atriði"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Þú hefur hreinsað allt"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Forritsupplýsingar"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"skjáfesting"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"leita"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Ekki var hægt að ræsa <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Slökkt er á <xliff:g id="APP">%s</xliff:g> í öruggri stillingu."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Hreinsa allt"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Dragðu hingað til að skipta skjánum"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Lárétt skipting"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Lóðrétt skipting"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Sérsniðin skipting"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Skipta skjá að ofanverðu"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Skipta skjá til vinstri"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Skipta skjá til hægri"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-it/strings.xml b/packages/SystemUI/legacy/recents/res/values-it/strings.xml
deleted file mode 100644
index e04d560..0000000
--- a/packages/SystemUI/legacy/recents/res/values-it/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Panoramica."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Elimina <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> eliminata."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Tutte le applicazioni recenti sono state rimosse."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Mostra informazioni sull\'applicazione <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Avvio di <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nessun elemento recente"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Hai cancellato tutto"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informazioni sull\'applicazione"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"blocco su schermo"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"cerca"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Impossibile avviare <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"L\'app <xliff:g id="APP">%s</xliff:g> è stata disattivata in modalità provvisoria."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Cancella tutto"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Trascina qui per utilizzare la modalità Schermo diviso"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Divisione in orizzontale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Divisione in verticale"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Divisione personalizzata"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Schermo diviso in alto"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Schermo diviso a sinistra"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Schermo diviso a destra"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-iw/strings.xml b/packages/SystemUI/legacy/recents/res/values-iw/strings.xml
deleted file mode 100644
index a96c709..0000000
--- a/packages/SystemUI/legacy/recents/res/values-iw/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"סקירה כללית."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"הסרה של <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> הוסרה."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"כל האפליקציות האחרונות הוסרו."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"פתיחת מידע על האפליקציה <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"מפעיל את <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"אין פריטים אחרונים"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"מחקת הכול"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"מידע על האפליקציה"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"הקפאת מסך"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"חיפוש"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"לא ניתן היה להפעיל את <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> מושבתת במצב בטוח."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ניקוי הכול"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"יש לגרור לכאן כדי להשתמש במסך מפוצל"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"פיצול אופקי"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"פיצול אנכי"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"פיצול מותאם אישית"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"פיצול מסך למעלה"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"פיצול מסך לשמאל"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"פיצול מסך לימין"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ja/strings.xml b/packages/SystemUI/legacy/recents/res/values-ja/strings.xml
deleted file mode 100644
index 4d7524c..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ja/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"最近"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g>を削除します。"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g>を削除しました。"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"最近のアプリをすべて削除しました。"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g>のアプリ情報を開きます。"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>を開始しています。"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"最近のアイテムはありません"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"すべてのタスクを削除しました"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"アプリ情報"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"画面固定"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"検索"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>を開始できませんでした。"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>はセーフモードでは無効になります。"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"すべて消去"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"分割画面を使用するにはここにドラッグします"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"横に分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"縦に分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"分割(カスタム)"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"画面を上に分割"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"画面を左に分割"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"画面を右に分割"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ka/strings.xml b/packages/SystemUI/legacy/recents/res/values-ka/strings.xml
deleted file mode 100644
index 088388b..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ka/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"მიმოხილვა"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g>-ის დახურვა."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> დაიხურა."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ყველა ბოლოდროინდელი აპლიკაცია დაიხურა."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> აპლიკაციის ინფორმაციის გახსნა."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"მიმდინარეობს <xliff:g id="APP">%s</xliff:g>-ის გაშვება."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ბოლოდროინდელი ერთეულები არ არის"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ყველაფერი გასუფთავდა"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"აპლიკაციის ინფორმაცია"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ეკრანზე ჩამაგრება"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ძიება"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>-ის გაშვება ვერ მოხერხდა."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> გათიშულია უსაფრთხო რეჟიმში."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ყველას გასუფთავება"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ეკრანის გასაყოფად ჩავლებით გადმოიტანეთ აქ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"ჰორიზონტალური გაყოფა"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ვერტიკალური გაყოფა"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"მორგებული გაყოფა"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ეკრანის გაყოფა ზემოთ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ეკრანის გაყოფა მარცხნივ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ეკრანის გაყოფა მარჯვნივ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-kk/strings.xml b/packages/SystemUI/legacy/recents/res/values-kk/strings.xml
deleted file mode 100644
index 9d4e01c..0000000
--- a/packages/SystemUI/legacy/recents/res/values-kk/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Жалпы ақпарат."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> қолданбасын өшіру."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> өшірілді."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Барлық қолданбалар \"Соңғылар\" тізімінен өшірілді."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> қолданбасы туралы ақпаратты ашу."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> іске қосылды."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ешқандай соңғы элементтер жоқ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Барлығын өшірдіңіз"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Қолданба туралы ақпарат"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"экранды бекіту"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"іздеу"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> іске қосылмады."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> қауіпсіз режимде өшіріледі."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Барлығын өшіру"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Экранды бөлу үшін осы жерге сүйреңіз"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Көлденеңінен бөлу"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Тігінен бөлу"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Бөлу (арнаулы)"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Экранды жоғары жағынан бөлу"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Экранды сол жағынан бөлу"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Экранды оң жағынан бөлу"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-km/strings.xml b/packages/SystemUI/legacy/recents/res/values-km/strings.xml
deleted file mode 100644
index b7bfba6..0000000
--- a/packages/SystemUI/legacy/recents/res/values-km/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ទិដ្ឋភាពរួម។"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"ច្រានចោល <xliff:g id="APP">%s</xliff:g> ។"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"បាន​ច្រានចោល <xliff:g id="APP">%s</xliff:g> ។"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"បាន​ច្រានចោល​កម្មវិធីថ្មីៗ​ទាំងអស់។"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"បើក​ព័ត៌មាន​កម្មវិធី <xliff:g id="APP">%s</xliff:g> ។"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"កំពុង​ចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> ។"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"មិនមានធាតុថ្មីៗទេ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"អ្នក​បានសម្អាត​អ្វីៗ​គ្រប់យ៉ាង"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ព័ត៌មាន​កម្មវិធី"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ការ​ភ្ជាប់​អេក្រង់"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ស្វែង​រក"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"មិន​អាច​ចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> បានទេ។"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ត្រូវបាន​បិទ​ដំណើរការ​ក្នុងមុខងារ​សុវត្ថិភាព។"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"សម្អាត​ទាំងអស់"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"អូសនៅទីនេះដើម្បីប្រើអេក្រង់បំបែក"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"បំបែកផ្តេក"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"បំបែកបញ្ឈរ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"បំបែកផ្ទាល់ខ្លួន"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"បំបែក​អេក្រង់​ទៅ​ខាងលើ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"បំបែក​អេក្រង់​ទៅ​ខាងឆ្វេង"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"បំបែក​អេក្រង់​ទៅ​ខាងស្តាំ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-kn/strings.xml b/packages/SystemUI/legacy/recents/res/values-kn/strings.xml
deleted file mode 100644
index 84894c1..0000000
--- a/packages/SystemUI/legacy/recents/res/values-kn/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ಸಮಗ್ರ ನೋಟ."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ವಜಾಗೊಳಿಸಿ."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ಇತ್ತೀಚಿನ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳನ್ನು ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ಆ್ಯಪ್ ಮಾಹಿತಿ ತೆರೆಯಿರಿ."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ನೀವು ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿರುವಿರಿ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ಸ್ಕ್ರೀನ್ ಪಿನ್ನಿಂಗ್"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ಹುಡುಕಾಟ"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ಪ್ರಾರಂಭಿಸಲು ಸಾದ್ಯವಾಗಲಿಲ್ಲ."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ಅನ್ನು ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ವಿಭಜಿತ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬಳಸಲು ಇಲ್ಲಿ ಡ್ರ್ಯಾಗ್‌ ಮಾಡಿ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"ಅಡ್ಡಲಾಗಿ ವಿಭಜಿಸಿದ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ಲಂಬವಾಗಿ ವಿಭಜಿಸಿದ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"ಕಸ್ಟಮ್ ವಿಭಜಿಸಿದ"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ಮೇಲ್ಭಾಗಕ್ಕೆ ಸ್ಕ್ರೀನ್ ಅನ್ನು ವಿಭಜಿಸಿ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ಎಡಕ್ಕೆ ಸ್ಕ್ರೀನ್ ಅನ್ನು ವಿಭಜಿಸಿ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ಬಲಕ್ಕೆ ಸ್ಕ್ರೀನ್ ಅನ್ನು ವಿಭಜಿಸಿ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ko/strings.xml b/packages/SystemUI/legacy/recents/res/values-ko/strings.xml
deleted file mode 100644
index ee856bd..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ko/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"최근 사용"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g>을(를) 닫습니다."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> 애플리케이션을 닫았습니다."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"최근 사용한 애플리케이션을 모두 닫았습니다."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> 애플리케이션 정보를 엽니다."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>을(를) 시작하는 중입니다."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"최근 항목이 없습니다."</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"모든 항목을 삭제했습니다."</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"애플리케이션 정보"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"화면 고정"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"검색"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>을(를) 시작할 수 없습니다."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>은(는) 안전 모드에서 사용 중지됩니다."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"모두 삭제"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"여기를 드래그하여 분할 화면 사용하기"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"수평 분할"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"수직 분할"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"맞춤 분할"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"위쪽으로 화면 분할"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"왼쪽으로 화면 분할"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"오른쪽으로 화면 분할"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ky/strings.xml b/packages/SystemUI/legacy/recents/res/values-ky/strings.xml
deleted file mode 100644
index 879e492..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ky/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Сереп салуу."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> колдонмосун өчүрүү."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> колдонмосу өчүрүлдү."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Акыркы колдонмолордун баары өчүрүлдү."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> колдонмосу жөнүндө маалыматты ачыңыз."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ачылууда."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Акыркы колдонмолор жок"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Баарын тазаладыңыз"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Колдонмо жөнүндө маалымат"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"экран кадоо"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"издөө"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> колдонмосу ачылган жок"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> коопсуз режиминде өчүрүлдү."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Баарын тазалоо"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Экранды бөлүү үчүн бул жерге сүйрөңүз"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Туурасынан бөлүү"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Тигинен бөлүү"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Ыңгайлаштырылган бөлүү"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Экранды өйдө жакка бөлүү"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Экранды сол жакка бөлүү"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Экранды оң жакка бөлүү"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-lo/strings.xml b/packages/SystemUI/legacy/recents/res/values-lo/strings.xml
deleted file mode 100644
index 17f56b4..0000000
--- a/packages/SystemUI/legacy/recents/res/values-lo/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ພາບຮວມ."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"ປິດ <xliff:g id="APP">%s</xliff:g> ໄວ້."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"ປິດ <xliff:g id="APP">%s</xliff:g> ແລ້ວ."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ທຸກແອັບພລິເຄຊັນບໍ່ດົນມານີ້ຖືກປິດໄວ້ແລ້ວ."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"ເປີດຂໍ້ມູນແອັບພລິເຄຊັນ <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"ກຳລັງເປີດ <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ທ່ານລຶບລ້າງທຸກຢ່າງແລ້ວ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ຂໍ້ມູນແອັບພລິເຄຊັນ"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ການປັກໝຸດໜ້າຈໍ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ຊອກຫາ"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"ບໍ່ສາມາດເລີ່ມ <xliff:g id="APP">%s</xliff:g> ໄດ້."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ຖືກປິດໃຊ້ໃນໂໝດຄວາມມປອດໄພ."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ລຶບລ້າງທັງໝົດ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ລາກມາບ່ອນນີ້ເພື່ອໃຊ້ການແບ່ງໜ້າຈໍ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"ການແຍກລວງຂວາງ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ການແຍກລວງຕັ້ງ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"ການແຍກກຳນົດເອງ"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ແຍກໜ້າຈໍໄປທາງເທິງ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ແຍກໜ້າຈໍໄປທາງຊ້າຍ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ແຍກໜ້າຈໍໄປທາງຂວາ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-lt/strings.xml b/packages/SystemUI/legacy/recents/res/values-lt/strings.xml
deleted file mode 100644
index 4a9eb83..0000000
--- a/packages/SystemUI/legacy/recents/res/values-lt/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Apžvalga."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Atsisakyti programos „<xliff:g id="APP">%s</xliff:g>“."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Atsisakyta programos „<xliff:g id="APP">%s</xliff:g>“."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Atsisakyta visų naujausių programų."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Atidaryti programos „<xliff:g id="APP">%s</xliff:g>“ informaciją."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Paleidžiama programa „<xliff:g id="APP">%s</xliff:g>“."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nėra jokių naujausių elementų"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Viską išvalėte"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Programos informacija"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ekrano prisegimas"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ieškoti"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Nepavyko paleisti programos „<xliff:g id="APP">%s</xliff:g>“."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Programa „<xliff:g id="APP">%s</xliff:g>“ išjungta saugos režimu."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Išvalyti viską"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Vilkite čia, kad naudotumėte skaidytą ekraną"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horizontalus skaidymas"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikalus skaidymas"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Tinkintas skaidymas"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Skaidyti ekraną į viršų"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Skaidyti ekraną į kairę"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Skaidyti ekraną į dešinę"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-lv/strings.xml b/packages/SystemUI/legacy/recents/res/values-lv/strings.xml
deleted file mode 100644
index 7d87e00..0000000
--- a/packages/SystemUI/legacy/recents/res/values-lv/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Pārskats."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Nerādīt lietotni <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Vairs netiek rādīta lietotne <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Vairs netiek rādīta neviena nesen izmantotā lietojumprogramma."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Atveriet lietojumprogrammas <xliff:g id="APP">%s</xliff:g> informāciju."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Notiek lietotnes <xliff:g id="APP">%s</xliff:g> palaišana."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nav nesenu vienumu"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Visi uzdevumi ir notīrīti"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Lietojumprogrammas informācija"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Piespraust ekrānu"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"Meklēt"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Nevarēja palaist lietotni <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Lietotne <xliff:g id="APP">%s</xliff:g> ir atspējota drošajā režīmā."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Notīrīt visu"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Velciet šeit, lai izmantotu ekrāna sadalīšanu"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horizontāls dalījums"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikāls dalījums"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Pielāgots dalījums"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Sadalīt ekrānu augšdaļā"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Sadalīt ekrānu kreisajā pusē"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Sadalīt ekrānu labajā pusē"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-mk/strings.xml b/packages/SystemUI/legacy/recents/res/values-mk/strings.xml
deleted file mode 100644
index d8ced0b..0000000
--- a/packages/SystemUI/legacy/recents/res/values-mk/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Преглед."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Отфрлете ја <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> е отфрлена."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Сите неодамнешни апликации се отфрлени."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Отворете информации за апликацијата <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Се стартува <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Нема неодамнешни ставки"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Избришавте сѐ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Информации за апликацијата"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"прикачување екран"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"пребарувај"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> не можеше да се стартува."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> е оневозможена во безбеден режим."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Избриши сѐ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Повлечете тука за да користите поделен екран"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Подели хоризонтално"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Подели вертикално"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Подели приспособено"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Подели го екранот во горниот дел"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Подели го екранот на левата страна"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Подели го екранот на десната страна"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ml/strings.xml b/packages/SystemUI/legacy/recents/res/values-ml/strings.xml
deleted file mode 100644
index 6dd797e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ml/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"അവലോകനം."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ഡിസ്‌മിസ് ചെയ്യുക."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ഡിസ്‌മിസ് ചെയ്‌തു."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"അടുത്തിടെയുള്ള എല്ലാ ആപ്പുകളും ഡിസ്‌മിസ് ചെയ്‌തു."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ആപ്പ് വിവരങ്ങൾ തുറക്കുക."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ആരംഭിക്കുന്നു."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"നിങ്ങൾ എല്ലാം മായ്ച്ചിരിക്കുന്നു"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ആപ്പ് വിവരങ്ങൾ"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"സ്ക്രീൻ പിൻ ചെയ്യൽ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"തിരയുക"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ആരംഭിക്കാനായില്ല"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"സുരക്ഷിത മോഡിൽ <xliff:g id="APP">%s</xliff:g> പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"എല്ലാം മായ്‌ക്കുക"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"സ്പ്ലിറ്റ് സ്ക്രീൻ ഉപയോഗിക്കാൻ, ഇവിടെ വലിച്ചിടുക"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"തിരശ്ചീനമായി സ്‌പ്ലിറ്റ് ചെയ്യുക"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ലംബമായി സ്‌പ്ലിറ്റ് ചെയ്യുക"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"ഇഷ്‌ടാനുസൃതമായി സ്‌പ്ലിറ്റ് ചെയ്യുക"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"സ്ക്രീൻ മുകളിലോട്ട് സ്പ്ലിറ്റ് ചെയ്യുക"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"സ്ക്രീൻ ഇടത്തോട്ട് സ്പ്ലിറ്റ് ചെയ്യുക"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"സ്ക്രീൻ വലത്തോട്ട് സ്‌പ്ലിറ്റ് ചെയ്യുക"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-mn/strings.xml b/packages/SystemUI/legacy/recents/res/values-mn/strings.xml
deleted file mode 100644
index 205f56c..0000000
--- a/packages/SystemUI/legacy/recents/res/values-mn/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Тойм."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g>-г үл хэрэгсэнэ үү."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g>-г үл хэрэгссэн."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Саяхны бүх аппыг үл хэрэгссэн."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> аппын мэдээллийг нээнэ үү."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>-г эхлүүлж байна."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Саяхны зүйлс алга"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Та бүгдийг нь устгасан"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Аппын мэдээлэл"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"дэлгэц тогтоох"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"хайх"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>-г эхлүүлж чадсангүй."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>-г аюулгүй горимд идэвхгүй болгосон."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Бүгдийг устгах"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Хуваасан дэлгэцийг ашиглахын тулд энд чирэх"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Хэвтээ чиглэлд хуваах"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Босоо чиглэлд хуваах"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Хүссэн хэлбэрээр хуваах"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Дэлгэцийг дээд хэсэгт хуваах"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Дэлгэцийг зүүн хэсэгт хуваах"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Дэлгэцийг баруун хэсэгт хуваах"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-mr/strings.xml b/packages/SystemUI/legacy/recents/res/values-mr/strings.xml
deleted file mode 100644
index 51bce6d..0000000
--- a/packages/SystemUI/legacy/recents/res/values-mr/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"अवलोकन."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> डिसमिस करा."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> डिसमिस केले"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"अलीकडील सर्व अॅप्लिकेशन डिसमिस झाले."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> अॅप्लिकेशन माहिती उघडा."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> सुरू करत आहे."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"कोणतेही अलीकडील आयटम नाहीत"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"तुम्ही सर्वकाही साफ केले"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"अॅप्लिकेशन माहिती"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"स्‍क्रीन पिन करणे"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"शोधा"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> सुरू करता आले नाही."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> सुरक्षित मोडमध्ये बंद केले आहे."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"सर्व साफ करा"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"स्प्लिट स्क्रीन वापर करण्यासाठी येथे ड्रॅग करा"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"आडवे स्प्लिट करा"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"उभे स्प्लिट करा"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"कस्टम स्प्लिट करा"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"स्क्रीन वर स्प्लिट करा"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"स्क्रीन डावीकडे स्प्लिट करा"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"स्क्रीन उजवीकडे स्प्लिट करा"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ms/strings.xml b/packages/SystemUI/legacy/recents/res/values-ms/strings.xml
deleted file mode 100644
index ae4461d..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ms/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Ikhtisar."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Ketepikan <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> diketepikan."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Semua aplikasi terbaharu diketepikan."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Buka maklumat aplikasi <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Memulakan <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Tiada item terbaharu"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Anda telah mengetepikan semua item"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Maklumat Aplikasi"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"penyematan skrin"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"cari"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Tidak dapat memulakan <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> dilumpuhkan dalam mod selamat."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Kosongkan semua"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Seret ke sini untuk menggunakan skrin pisah"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Pisah Mendatar"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Pisah Menegak"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Pisah Tersuai"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Pisahkan skrin ke atas"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Pisahkan skrin ke kiri"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Pisahkan skrin ke kanan"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-my/strings.xml b/packages/SystemUI/legacy/recents/res/values-my/strings.xml
deleted file mode 100644
index 7b5870e1..0000000
--- a/packages/SystemUI/legacy/recents/res/values-my/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"အနှစ်ချုပ်။"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ကို ပယ်မည်။"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ကို ဖယ်ထုတ်ထားသည်။"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"လတ်တလော အပလီကေးရှင်းအားလုံး ဖယ်ထုတ်ထားသည်။"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> အပလီကေးရှင်း အချက်အလက်ကို ဖွင့်မည်။"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ကို စတင်နေသည်။"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"လတ်တလော ဖွင့်ထားသည်များ မရှိပါ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"အားလုံးကို ဖယ်ရှားပြီးပါပြီ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"အပလီကေးရှင်း အချက်အလက်"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"မျက်နှာပြင် ပင်ထိုးမှု"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ရှာရန်"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ကို စတင်၍ မရပါ။"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"အန္တရာယ်ကင်းမှု စနစ်တွင် <xliff:g id="APP">%s</xliff:g> ကို ပိတ်ထားပါသည်။"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"အားလုံး ဖယ်ရှားရန်"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို အသုံးပြုရန် ဤနေရာသို့ ဖိဆွဲပါ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"အလျားလိုက် ခွဲရန်"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ဒေါင်လိုက် ခွဲရန်"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"စိတ်ကြိုက် ခွဲရန်"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"မျက်နှာပြင်ကို အပေါ်သို့ ခွဲရန်"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"မျက်နှာပြင်ကို ဘယ်ဘက်သို့ ခွဲရန်"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"မျက်နှာပြင်ကို ညာဘက်သို့ ခွဲရန်"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-nb/strings.xml b/packages/SystemUI/legacy/recents/res/values-nb/strings.xml
deleted file mode 100644
index 176986a..0000000
--- a/packages/SystemUI/legacy/recents/res/values-nb/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Oversikt."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Avvis <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> er avvist."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alle nylig brukte apper er avvist."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Åpne appinformasjonen for <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Starter <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ingen nylige elementer"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Du har fjernet alt"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Appinformasjon"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"én-appsmodus"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"søk"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Kunne ikke starte <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> er slått av i sikker modus."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Fjern alt"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Dra hit for å bruke delt skjerm"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Del horisontalt"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Del vertikalt"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Del tilpasset"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Delt skjerm øverst"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Delt skjerm til venstre"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Delt skjerm til høyre"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ne/strings.xml b/packages/SystemUI/legacy/recents/res/values-ne/strings.xml
deleted file mode 100644
index 0113833..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ne/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"परिदृश्य।"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> खारेज गर्नुहोस्।"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> खारेज गरिएको छ।"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"हालका सबै अनुप्रयोगहरू खारेज गरियो।"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> अनुप्रयोग सम्बन्धी जानकारी खोल्नुहोस्।"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> सुरु गर्दै।"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"हालसालैको कुनै पनि वस्तु छैन"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"तपाईंले सबै कुरा खाली गर्नुभएको छ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"अनुप्रयोगको जानकारी"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"स्क्रिन पिनिसङ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"खोज्नुहोस्"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> सुरु गर्न सकिएन।"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> लाई सुरक्षित मोडमा असक्षम पारिएको छ।"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"सबै हटाउनुहोस्"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"विभाजित स्क्रिनको प्रयोग गर्न यहाँ तान्नुहोस्"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"तेर्सो रूपमा विभाजन गर्नुहोस्"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ठाडो रूपमा विभाजन गर्नुहोस्"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"आफू अनुकूल विभाजन गर्नुहोस्"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"विभाजित स्क्रिन शीर्ष स्थानमा राख्नुहोस्‌"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"विभाजित स्क्रिन बायाँतर्फ राख्नुहोस्‌"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"विभाजित स्क्रिन दायाँतर्फ राख्नुहोस्‌"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-nl/strings.xml b/packages/SystemUI/legacy/recents/res/values-nl/strings.xml
deleted file mode 100644
index 9714022..0000000
--- a/packages/SystemUI/legacy/recents/res/values-nl/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overzicht."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> sluiten."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> verwijderd."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alle recente apps gesloten."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"App-gegevens voor <xliff:g id="APP">%s</xliff:g> openen."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> starten."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Geen recente items"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Je hebt alles gewist"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"App-informatie"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"scherm vastzetten"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"zoeken"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Kan <xliff:g id="APP">%s</xliff:g> niet starten."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> is uitgeschakeld in de veilige modus"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Alles wissen"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Sleep hier naartoe om het scherm te splitsen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horizontaal splitsen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Verticaal splitsen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Aangepast splitsen"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Scherm bovenaan gesplitst"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Scherm links gesplitst"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Scherm rechts gesplitst"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-or/strings.xml b/packages/SystemUI/legacy/recents/res/values-or/strings.xml
deleted file mode 100644
index 7ffcc94..0000000
--- a/packages/SystemUI/legacy/recents/res/values-or/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ସଂକ୍ଷିପ୍ତ ବିବରଣୀ"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ଖାରଜ।"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ଖାରଜ କରିଦିଆଗଲା।"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ସମସ୍ତ ସମ୍ପ୍ରତି ଆପ୍ଲିକେସନ୍‍ଗୁଡ଼ିକ ଖାରଜ କରାଯାଇଛି।"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ଆପ୍ଲିକେସନ୍‍ ସୂଚନା ଖୋଲନ୍ତୁ।"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ଆରମ୍ଭ ହେଉଛି।"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"କୌଣସି ସାମ୍ପ୍ରତିକ ଆଇଟମ୍ ନାହିଁ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ଆପଣ ସୁବୁକିଛି ଖାଲି କରିଦେଇଛନ୍ତି"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ଆପ୍ଲିକେସନ୍‍ ସୂଚନା"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ସ୍କ୍ରିନ୍‌ ଲକ୍‌"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ଖୋଜନ୍ତୁ"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> କୁ ଆରମ୍ଭ କରାଯାଇପାରିଲା ନାହିଁ।"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ସୁରକ୍ଷିତ-ମୋଡ୍‌ରେ ଅକ୍ଷମ ଅଟେ।"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ସ୍ପ୍ଲିଟ୍‍ ସ୍କ୍ରିନ୍‍ ବ୍ୟବହାର କରିବା ପାଇଁ ଏଠାକୁ ଡ୍ରାଗ୍‌ କରନ୍ତୁ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"ଭୂସମାନ୍ତରଭାବରେ ଭାଗ କରନ୍ତୁ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ଭୂଲମ୍ବଭାବରେ ଭାଗ କରନ୍ତୁ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"କଷ୍ଟମ୍‍ କରି ଭାଗ କରନ୍ତୁ"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ସ୍କ୍ରିନ୍‌କୁ ଉପର ଆଡ଼କୁ ଭାଗ କରନ୍ତୁ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ସ୍କ୍ରିନ୍‌କୁ ବାମ ଆଡ଼କୁ ଭାଗ କରନ୍ତୁ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ସ୍କ୍ରିନ୍‌କୁ ଡାହାଣ ଆଡ଼କୁ ଭାଗ କରନ୍ତୁ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-pa/strings.xml b/packages/SystemUI/legacy/recents/res/values-pa/strings.xml
deleted file mode 100644
index 4608561..0000000
--- a/packages/SystemUI/legacy/recents/res/values-pa/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ਰੂਪ-ਰੇਖਾ।"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ਨੂੰ ਖਾਰਜ ਕਰੋ।"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ਖਾਰਜ ਕੀਤੀ ਗਈ।"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ਸਾਰੀਆਂ ਹਾਲੀਆ ਐਪਲੀਕੇਸ਼ਨਾਂ ਖਾਰਜ ਕੀਤੀਆਂ ਗਈਆਂ।"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ਐਪਲੀਕੇਸ਼ਨਾਂ ਜਾਣਕਾਰੀ ਖੋਲ੍ਹੋ।"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ਚਾਲੂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ।"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮਾਂ ਨਹੀਂ"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ਤੁਸੀਂ ਸਭ ਕੁਝ ਸਾਫ਼ ਕਰ ਦਿੱਤਾ ਹੈ"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ਐਪਲੀਕੇਸ਼ਨ ਜਾਣਕਾਰੀ"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ਸਕ੍ਰੀਨ ਪਿਨਿੰਗ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ਖੋਜ"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕੇ।"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ਨੂੰ ਸੁਰੱਖਿਅਤ-ਮੋਡ ਵਿੱਚ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ।"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਇੱਥੇ ਘਸੀਟੋ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"ਲੇਟਵੀਂ ਵੰਡ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"ਖੜ੍ਹਵੀਂ ਵੰਡ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"ਵਿਉਂਤੀ ਵੰਡ"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"ਸਕ੍ਰੀਨ ਨੂੰ ਉੱਪਰ ਵੱਲ ਵੰਡੋ"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"ਸਕ੍ਰੀਨ ਨੂੰ ਖੱਬੇ ਪਾਸੇ ਵੰਡੋ"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"ਸਕ੍ਰੀਨ ਨੂੰ ਸੱਜੇ ਪਾਸੇ ਵੰਡੋ"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-pl/strings.xml b/packages/SystemUI/legacy/recents/res/values-pl/strings.xml
deleted file mode 100644
index 50b4ad0..0000000
--- a/packages/SystemUI/legacy/recents/res/values-pl/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Przegląd."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Zamknij aplikację <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikacja <xliff:g id="APP">%s</xliff:g> została zamknięta."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Wszystkie ostatnie aplikacje zostały zamknięte."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otwórz informacje o aplikacji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Uruchamiam aplikację <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Brak ostatnich elementów"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Wszystko zostało wyczyszczone"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informacje o aplikacji"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"przypinanie ekranu"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"szukaj"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Nie udało się uruchomić aplikacji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikacja <xliff:g id="APP">%s</xliff:g> została wyłączona w trybie bezpiecznym."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Wyczyść wszystko"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Przeciągnij tutaj, by podzielić ekran"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Podziel poziomo"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Podziel pionowo"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Podziel niestandardowo"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Podziel ekran u góry"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Podziel ekran z lewej"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Podziel ekran z prawej"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml b/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml
deleted file mode 100644
index b557ad2..0000000
--- a/packages/SystemUI/legacy/recents/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Visão geral."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dispensar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dispensado."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Todos os aplicativos recentes foram dispensados."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Abre informações do aplicativo <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nenhum item recente"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Você limpou tudo"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informações do aplicativo"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Fixar tela"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pesquisar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"O app <xliff:g id="APP">%s</xliff:g> fica desativado no modo de segurança."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Limpar tudo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arraste aqui para usar a tela dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Divisão horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Divisão vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Divisão personalizada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dividir a tela para a parte superior"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dividir a tela para a esquerda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dividir a tela para a direita"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-pt-rPT/strings.xml b/packages/SystemUI/legacy/recents/res/values-pt-rPT/strings.xml
deleted file mode 100644
index e62e1c6..0000000
--- a/packages/SystemUI/legacy/recents/res/values-pt-rPT/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Vista geral."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Ignorar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplicação <xliff:g id="APP">%s</xliff:g> ignorada."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Todas as aplicações recentes foram ignoradas."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Abrir as informações da aplicação <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"A iniciar a aplicação <xliff:g id="APP">%s</xliff:g>…"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nenhum item recente"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Limpou tudo"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informações da aplicação"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"afixação no ecrã"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pesquisar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Não foi possível iniciar a aplicação <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"A aplicação <xliff:g id="APP">%s</xliff:g> está desativada no modo de segurança."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Limpar tudo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arraste aqui para utilizar o ecrã dividido"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Divisão horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Divisão vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Divisão personalizada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Ecrã dividido na parte superior"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Ecrã dividido à esquerda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Ecrã dividido à direita"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-pt/strings.xml b/packages/SystemUI/legacy/recents/res/values-pt/strings.xml
deleted file mode 100644
index b557ad2..0000000
--- a/packages/SystemUI/legacy/recents/res/values-pt/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Visão geral."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Dispensar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> dispensado."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Todos os aplicativos recentes foram dispensados."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Abre informações do aplicativo <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nenhum item recente"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Você limpou tudo"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informações do aplicativo"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"Fixar tela"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"pesquisar"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"O app <xliff:g id="APP">%s</xliff:g> fica desativado no modo de segurança."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Limpar tudo"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Arraste aqui para usar a tela dividida"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Divisão horizontal"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Divisão vertical"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Divisão personalizada"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Dividir a tela para a parte superior"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Dividir a tela para a esquerda"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Dividir a tela para a direita"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ro/strings.xml b/packages/SystemUI/legacy/recents/res/values-ro/strings.xml
deleted file mode 100644
index 7f8f018..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ro/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Recente."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Închideți <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> a fost închisă."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Toate aplicațiile recente au fost închise."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Deschideți informațiile despre aplicația <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Se inițiază <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Niciun element recent"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Ați șters tot"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informații despre aplicație"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fixare pe ecran"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"căutați"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> nu a putut porni."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplicația <xliff:g id="APP">%s</xliff:g> este dezactivată în modul sigur."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ștergeți tot"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Trageți aici pentru a folosi ecranul împărțit"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Împărțiți pe orizontală"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Împărțiți pe verticală"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Împărțiți personalizat"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Împărțiți ecranul în partea de sus"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Împărțiți ecranul la stânga"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Împărțiți ecranul la dreapta"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ru/strings.xml b/packages/SystemUI/legacy/recents/res/values-ru/strings.xml
deleted file mode 100644
index 1e988bb..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ru/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Обзор."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Удалить приложение <xliff:g id="APP">%s</xliff:g> из списка."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Приложение <xliff:g id="APP">%s</xliff:g> удалено из списка."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Все недавние приложения удалены из списка."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Открыть информацию о приложении <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Запуск приложения <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Здесь пока ничего нет."</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Список пуст."</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Сведения о приложении"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"блокировка в приложении"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"поиск"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Не удалось запустить приложение \"<xliff:g id="APP">%s</xliff:g>\"."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Приложение \"<xliff:g id="APP">%s</xliff:g>\" отключено в безопасном режиме."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Удалить все"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Перетащите сюда, чтобы разделить экран"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Разделить по горизонтали"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Разделить по вертикали"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Разделить по-другому"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Разделить экран по верхнему краю"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Разделить экран по левому краю"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Разделить экран по правому краю"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-si/strings.xml b/packages/SystemUI/legacy/recents/res/values-si/strings.xml
deleted file mode 100644
index cae8357..0000000
--- a/packages/SystemUI/legacy/recents/res/values-si/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"දළ විශ්ලේෂණය."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ඉවත ලන්න."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ඉවත දමා ඇත."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"සියලුම මෑත යෙඳුම් ඉවත ලන ලදී."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> යෙදුම් තොරතුරු විවෘත කරයි."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ආරම්භ කරමින්."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"මෑත අයිතම නැත"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"ඔබ සියලු දේ හිස් කර ඇත"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"යෙදුම් තොරතුරු"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"තිර ඇමිණීම"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"සෙවීම"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ආරම්භ කළ නොහැකි විය."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ආරක්ෂිත ප්‍රකාරය තුළ අබලයි."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"සියල්ල හිස් කරන්න"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"බෙදුම් තිරය භාවිත කිරීමට මෙතැනට අදින්න"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"තිරස්ව වෙන් කරන්න"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"සිරස්ව වෙන් කරන්න"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"අභිමත ලෙස වෙන් කරන්න"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"තිරය ඉහළට බෙදන්න"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"තිරය වමට බෙදන්න"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"තිරය දකුණට බෙදන්න"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sk/strings.xml b/packages/SystemUI/legacy/recents/res/values-sk/strings.xml
deleted file mode 100644
index 9c3a857..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sk/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Prehľad"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Zavrieť aplikáciu <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikácia <xliff:g id="APP">%s</xliff:g> bola zrušená."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Všetky nedávne aplikácie boli zrušené."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Otvoriť informácie o aplikácii <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Spúšťa sa aplikácia <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Žiadne nedávne položky"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Vymazali ste všetko"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informácie o aplikácii"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"pripnutie obrazovky"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"hľadať"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Aplikáciu <xliff:g id="APP">%s</xliff:g> sa nepodarilo spustiť."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikácia <xliff:g id="APP">%s</xliff:g> je v núdzovom režime zakázaná."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Vymazať všetko"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Presuňte okno sem a použite tak rozdelenú obrazovku"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Rozdeliť vodorovné"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Rozdeliť zvislé"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Rozdeliť vlastné"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Rozdelená obrazovka hore"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Rozdelená obrazovka naľavo"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Rozdelená obrazovka napravo"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sl/strings.xml b/packages/SystemUI/legacy/recents/res/values-sl/strings.xml
deleted file mode 100644
index 56b2ddb..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sl/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Pregled."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Opustitev aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Aplikacija <xliff:g id="APP">%s</xliff:g> je bila odstranjena."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Vse nedavne aplikacije so bile opuščene."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Odpiranje podatkov o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Zaganjanje aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Ni nedavnih elementov"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Vse ste počistili"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Podatki o aplikaciji"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"pripenjanje zaslona"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"išči"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Aplikacije <xliff:g id="APP">%s</xliff:g> ni bilo mogoče zagnati."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Aplikacija <xliff:g id="APP">%s</xliff:g> je v varnem načinu onemogočena."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Počisti vse"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Povlecite sem za razdeljeni zaslon"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Razdeli vodoravno"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Razdeli navpično"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Razdeli po meri"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Razdeljen zaslon na vrhu"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Razdeljen zaslon na levi"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Razdeljen zaslon na desni"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sq/strings.xml b/packages/SystemUI/legacy/recents/res/values-sq/strings.xml
deleted file mode 100644
index 48aab37..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sq/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Përmbledhja."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Largo <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> është hequr."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Të gjitha aplikacionet e fundit u larguan."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Hap informacionin e aplikacionit <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Po nis <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Nuk ka asnjë artikull të fundit"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"I ke pastruar të gjitha"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Informacioni i aplikacionit"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"kyçja e ekranit"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"kërko"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> nuk mund të nisej."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> është i çaktivizuar në modalitetin e sigurt."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Pastroji të gjitha"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Zvarrit këtu për të përdorur ekranin e ndarë"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Horizontal i ndarë"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikal i ndarë"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"I personalizuar i ndarë"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Ndaje ekranin lart"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Ndaje ekranin në të majtë"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Ndaje ekranin në të djathtë"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sr/strings.xml b/packages/SystemUI/legacy/recents/res/values-sr/strings.xml
deleted file mode 100644
index 9d5f126..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sr/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Преглед."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Одбаците апликацију <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Апликација <xliff:g id="APP">%s</xliff:g> је одбачена."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Све недавно коришћене апликације су одбачене."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Отворите информације о апликацији <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Покреће се <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Нема недавних ставки"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Обрисали сте све"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Информације о апликацији"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"качење екрана"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"претражи"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Покретање апликације <xliff:g id="APP">%s</xliff:g> није успело."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Апликација <xliff:g id="APP">%s</xliff:g> је онемогућена у безбедном режиму."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Обриши све"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Превуците овде да бисте користили раздељени екран"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Подели хоризонтално"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Подели вертикално"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Подели прилагођено"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Подели екран нагоре"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Подели екран налево"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Подели екран надесно"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sv/strings.xml b/packages/SystemUI/legacy/recents/res/values-sv/strings.xml
deleted file mode 100644
index b2ee34f..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sv/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Översikt."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Ta bort <xliff:g id="APP">%s</xliff:g> från listan."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> togs bort från listan."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Alla appar har tagits bort från listan Senaste."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Öppna appinformation för <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Startar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Listan är tom"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Du har tömt listan"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Appinformation"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"fästa skärmen"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"sök"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Det gick inte att starta appen <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> är inaktiverad i säkert läge."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Rensa alla"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Dra hit för att dela upp skärmen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Dela vågrätt"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Dela lodrätt"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Dela anpassat"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Delad skärm till överkanten"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Delad skärm åt vänster"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Delad skärm åt höger"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sw/strings.xml b/packages/SystemUI/legacy/recents/res/values-sw/strings.xml
deleted file mode 100644
index 49e7fb8..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sw/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Muhtasari."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Ondoa <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> imeondolewa."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Programu za hivi majuzi zimeondolewa."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Fungua maelezo kuhusu programu ya <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Inaanzisha <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Hakuna vipengee vya hivi majuzi"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Umeondoa vipengee vyote"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Maelezo ya Programu"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"kubandika kwenye skirini"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"tafuta"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Imeshindwa kuanzisha <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> imezimwa katika hali salama."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ondoa zote"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Buruta hapa ili utumie skrini iliyogawanywa"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Gawanya Mlalo"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Gawanya Wima"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Maalum Iliyogawanywa"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Gawa skrini kuelekea juu"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Gawa skrini upande wa kushoto"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Gawa skrini upande wa kulia"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-sw600dp/dimens.xml b/packages/SystemUI/legacy/recents/res/values-sw600dp/dimens.xml
deleted file mode 100644
index 20d6670..0000000
--- a/packages/SystemUI/legacy/recents/res/values-sw600dp/dimens.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2012, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-*/
--->
-<resources>
-    <!-- The offsets the tasks animate from when recents is launched while docking -->
-    <dimen name="recents_task_stack_animation_launched_while_docking_offset">192dp</dimen>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ta/strings.xml b/packages/SystemUI/legacy/recents/res/values-ta/strings.xml
deleted file mode 100644
index 91643fd..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ta/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"மேலோட்டப் பார்வை."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> ஆப்ஸை அகற்றும்."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> அகற்றப்பட்டது."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"அனைத்துச் சமீபத்திய ஆப்ஸும் அகற்றப்பட்டன."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ஆப்ஸ் பற்றிய தகவலைத் திறக்கும்."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ஆப்ஸைத் தொடங்குகிறது."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"சமீபத்தியவை எதுவுமில்லை"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"அனைத்தையும் அழித்துவிட்டீர்கள்"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ஆப்ஸ் பற்றிய தகவல்"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"திரையைப் பின் செய்"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"தேடு"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ஆப்ஸைத் தொடங்க இயலவில்லை."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"பாதுகாப்புப் பயன்முறையில் <xliff:g id="APP">%s</xliff:g> முடக்கப்பட்டது."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"அனைத்தையும் அழி"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"\'திரைப் பிரிப்பைப்\' பயன்படுத்த இங்கே இழுக்கவும்"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"கிடைமட்டமாகப் பிரி"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"செங்குத்தாகப் பிரி"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"தனிப்பயன் விருப்பத்தில் பிரி"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"திரையை மேற்புறமாகப் பிரிக்கும்"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"திரையை இடப்புறமாகப் பிரிக்கும்"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"திரையை வலப்புறமாகப் பிரிக்கும்"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-te/strings.xml b/packages/SystemUI/legacy/recents/res/values-te/strings.xml
deleted file mode 100644
index ea4e638..0000000
--- a/packages/SystemUI/legacy/recents/res/values-te/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"అవలోకనం."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g>ని తీసివేయండి."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> తీసివేయబడింది."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"అన్ని ఇటీవలి యాప్‌లు తీసివేయబడ్డాయి."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> యాప్ సమాచారాన్ని తెరుస్తుంది."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g>ని ప్రారంభిస్తోంది."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ఇటీవలి అంశాలు ఏవీ లేవు"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"మీరు అన్నింటినీ తీసివేసారు"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"యాప్ సమాచారం"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"స్క్రీన్‌కు పిన్ చేయడం"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"వెతుకు"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g>ని ప్రారంభించడం సాధ్యపడలేదు."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> సురక్షిత-మోడ్‌లో నిలిపివేయబడింది."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"అన్నీ తీసివేయి"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"విభజన స్క్రీన్‌ను ఉపయోగించడానికి ఇక్కడ లాగండి"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"అడ్డంగా విభజించు"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"నిలువుగా విభజించు"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"అనుకూలంగా విభజించు"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"స్క్రీన్‌ని ఎగువకు విభజించు"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"స్క్రీన్‌ని ఎడమ వైపుకి విభజించు"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"స్క్రీన్‌ని కుడి వైపుకి విభజించు"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-th/strings.xml b/packages/SystemUI/legacy/recents/res/values-th/strings.xml
deleted file mode 100644
index b88d05e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-th/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"ภาพรวม"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"ยกเลิก <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> ถูกนำออกไปแล้ว"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"ปิดแอปพลิเคชันล่าสุดทั้งหมดแล้ว"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"เปิดข้อมูลแอปพลิเคชัน <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"กำลังเริ่มต้น <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"ไม่มีรายการล่าสุด"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"คุณได้ล้างทุกอย่างแล้ว"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ข้อมูลแอปพลิเคชัน"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"การตรึงหน้าจอ"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ค้นหา"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"เริ่มใช้ <xliff:g id="APP">%s</xliff:g> ไม่ได้"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> ปิดใช้ในโหมดปลอดภัย"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"ล้างทั้งหมด"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"ลากมาที่นี่เพื่อใช้การแยกหน้าจอ"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"แยกในแนวนอน"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"แยกในแนวตั้ง"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"แยกแบบกำหนดเอง"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"แยกหน้าจอไปด้านบน"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"แยกหน้าจอไปทางซ้าย"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"แยกหน้าจอไปทางขวา"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-tl/strings.xml b/packages/SystemUI/legacy/recents/res/values-tl/strings.xml
deleted file mode 100644
index d940d4e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-tl/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Overview"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"I-dismiss ang <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Na-dismiss ang <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Na-dismiss ang lahat ng kamakailang application."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Buksan ang impormasyon ng <xliff:g id="APP">%s</xliff:g> application."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Sinisimulan ang <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Walang kamakailang item"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Na-clear mo ang lahat"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Impormasyon ng Application"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"pag-pin sa screen"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"hanapin"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Hindi masimulan ang <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Naka-disable ang <xliff:g id="APP">%s</xliff:g> sa safe-mode."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"I-clear lahat"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"I-drag dito para magamit ang split screen"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"I-split Pahalang"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"I-split Patayo"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Split Custom"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"I-split ang screen pataas"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"I-split ang screen pakaliwa"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"I-split ang screen pakanan"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-tr/strings.xml b/packages/SystemUI/legacy/recents/res/values-tr/strings.xml
deleted file mode 100644
index 982c57e..0000000
--- a/packages/SystemUI/legacy/recents/res/values-tr/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Genel Bakış."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> uygulamasını kapatır."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> kaldırıldı."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Tüm son uygulamalar kapatıldı."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> uygulama bilgilerini açar."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> başlatılıyor."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Yeni öğe yok"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Her şeyi sildiniz"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Uygulama Bilgileri"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ekran sabitleme"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"ara"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> başlatılamadı."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>, güvenli modda devre dışıdır."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Tümünü temizle"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Ekranı bölünmüş olarak kullanmak için buraya sürükleyin"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Yatay Ayırma"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Dikey Ayırma"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Özel Ayırma"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Ekranı yukarıya doğru böl"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Ekranı sola doğru böl"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Ekranı sağa doğru böl"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-uk/strings.xml b/packages/SystemUI/legacy/recents/res/values-uk/strings.xml
deleted file mode 100644
index 0c0b709..0000000
--- a/packages/SystemUI/legacy/recents/res/values-uk/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Огляд."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Закрити додаток <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Додаток <xliff:g id="APP">%s</xliff:g> закрито."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Усі останні додатки закрито."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Відкрити інформацію про додаток <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Запуск додатка <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Немає останніх елементів"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Ви очистили все"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Інформація про додаток"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"закріпити екран"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"пошук"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Не вдалося запустити додаток <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Додаток <xliff:g id="APP">%s</xliff:g> вимкнено в безпечному режимі."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Очистити все"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Перетягніть сюди, щоб розділити екран"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Розділити горизонтально"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Розділити вертикально"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Розділити (власний варіант)"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Розділити екран угору"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Розділити екран уліво"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Розділити екран управо"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-ur/strings.xml b/packages/SystemUI/legacy/recents/res/values-ur/strings.xml
deleted file mode 100644
index 46033da..0000000
--- a/packages/SystemUI/legacy/recents/res/values-ur/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"عمومی جائزہ۔"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"<xliff:g id="APP">%s</xliff:g> کو مسترد کریں۔"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> کو مسترد کر دیا گیا۔"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"سبھی حالیہ ایپلیکیشنز کو مسترد کر دیا گیا۔"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ایپلیکیشن کی معلومات کھولیں۔"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> شروع ہو رہی ہے۔"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"کوئی حالیہ آئٹم نہیں"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"آپ نے سب کچھ صاف کر دیا ہے"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"ایپلیکیشن کی معلومات"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"اسکرین کو پن کرنا"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"تلاش کریں"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> کو شروع نہیں کیا جا سکا۔"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"محفوظ موڈ میں <xliff:g id="APP">%s</xliff:g> غیر فعال ہے۔"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"سبھی کو ہٹائیں"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"اسپلٹ اسکرین استعمال کرنے کے لیے یہاں گھسیٹیں"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"بلحاظ افقی تقسیم کریں"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"بلحاظ عمودی تقسیم کریں"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"بلحاظ حسب ضرورت تقسیم کریں"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"اسکرین کو اوپر کی جانب تقسیم کریں"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"اسکرین کو بائیں جانب تقسیم کریں"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"اسکرین کو دائیں جانب تقسیم کریں"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-uz/strings.xml b/packages/SystemUI/legacy/recents/res/values-uz/strings.xml
deleted file mode 100644
index 6f8b153..0000000
--- a/packages/SystemUI/legacy/recents/res/values-uz/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Umumiy nazar."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Olib tashlash: <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"<xliff:g id="APP">%s</xliff:g> olib tashlangan."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Yaqinda ishlatilgan barcha ilovalar olib tashlandi."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"<xliff:g id="APP">%s</xliff:g> ilovasi haqidagi axborotlarni ochadi."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"<xliff:g id="APP">%s</xliff:g> ishga tushirilmoqda."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Yaqinda ishlatilgan ilovalar yoʻq"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Hammasi tozalandi"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Ilova haqida axborot"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ekranni mahkamlash"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"qidiruv"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"<xliff:g id="APP">%s</xliff:g> ilovasi ishga tushmadi."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"Xavfsiz rejimda <xliff:g id="APP">%s</xliff:g> ilovasi yopildi."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Ha"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Ekranni boʻlish xususiyatidan foydalanish uchun bu yerga torting"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Gorizontal yoʻnalishda boʻlish"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Vertikal yoʻnalishda boʻlish"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Boshqa usulda boʻlish"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Ekranni tepaga qadash"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Ekranni chap tomonga qadash"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Ekranni oʻng tomonga qadash"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-vi/strings.xml b/packages/SystemUI/legacy/recents/res/values-vi/strings.xml
deleted file mode 100644
index f672a3d..0000000
--- a/packages/SystemUI/legacy/recents/res/values-vi/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Tổng quan."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Loại bỏ <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"Đã loại bỏ <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Đã loại bỏ tất cả các ứng dụng gần đây."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Mở thông tin ứng dụng <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Khởi động <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Không có mục gần đây nào"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Bạn đã xóa mọi nội dung"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Thông tin ứng dụng"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"khóa màn hình"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"tìm kiếm"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Không thể khởi động <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g> bị tắt ở chế độ an toàn."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Xóa tất cả"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Kéo vào đây để sử dụng chế độ chia đôi màn hình"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Phân tách ngang"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Phân tách dọc"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Tùy chỉnh phân tách"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Chia đôi màn hình lên trên"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Chia đôi màn hình sang trái"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Chia đôi màn hình sang phải"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-zh-rCN/strings.xml b/packages/SystemUI/legacy/recents/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 993bfae..0000000
--- a/packages/SystemUI/legacy/recents/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"概览。"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"移除<xliff:g id="APP">%s</xliff:g>。"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"已移除<xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"已关闭所有最近用过的应用。"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"打开<xliff:g id="APP">%s</xliff:g>应用信息。"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"正在启动<xliff:g id="APP">%s</xliff:g>。"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"近期没有任何内容"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"您已清除所有内容"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"应用信息"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"固定屏幕"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"搜索"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"无法启动<xliff:g id="APP">%s</xliff:g>。"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"<xliff:g id="APP">%s</xliff:g>已在安全模式下停用。"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"全部清除"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"拖动到此处即可使用分屏功能"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"水平分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"垂直分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"自定义分割"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"将屏幕分隔线移到上方"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"将屏幕分隔线移到左侧"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"将屏幕分隔线移到右侧"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-zh-rHK/strings.xml b/packages/SystemUI/legacy/recents/res/values-zh-rHK/strings.xml
deleted file mode 100644
index b93d246..0000000
--- a/packages/SystemUI/legacy/recents/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"概覽。"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"關閉「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"「<xliff:g id="APP">%s</xliff:g>」已關閉。"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"所有最近使用的應用程式均已關閉。"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"開啟「<xliff:g id="APP">%s</xliff:g>」應用程式的資料。"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"正在啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"最近沒有任何項目"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"您已清除所有工作"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"應用程式資料"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"螢幕固定"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"搜尋"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"「<xliff:g id="APP">%s</xliff:g>」在安全模式下為停用狀態。"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"全部清除"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"拖曳這裡即可分割螢幕"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"水平分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"垂直分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"自訂分割"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"將分割畫面顯示喺頂部"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"將分割畫面顯示喺左邊"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"將分割畫面顯示喺右邊"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-zh-rTW/strings.xml b/packages/SystemUI/legacy/recents/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 54d656d..0000000
--- a/packages/SystemUI/legacy/recents/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"總覽。"</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"關閉「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"「<xliff:g id="APP">%s</xliff:g>」已關閉。"</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"最近使用的應用程式已全部關閉。"</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"開啟「<xliff:g id="APP">%s</xliff:g>」應用程式資訊。"</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"正在啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"最近沒有任何項目"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"你已清除所有工作"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"應用程式資訊"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"螢幕固定"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"搜尋"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"「<xliff:g id="APP">%s</xliff:g>」在安全模式中為停用狀態。"</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"全部清除"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"拖曳到這裡即可使用分割畫面"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"水平分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"垂直分割"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"自訂分割"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"將分割畫面顯示在頂端"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"將分割畫面顯示在左邊"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"將分割畫面顯示在右邊"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values-zu/strings.xml b/packages/SystemUI/legacy/recents/res/values-zu/strings.xml
deleted file mode 100644
index 9cbc439..0000000
--- a/packages/SystemUI/legacy/recents/res/values-zu/strings.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="accessibility_desc_recent_apps" msgid="2427210347871321373">"Buka konke."</string>
-    <string name="accessibility_recents_item_will_be_dismissed" msgid="2355882496933479534">"Cashisa i-<xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_dismissed" msgid="4816790842084268400">"I-<xliff:g id="APP">%s</xliff:g> icashisiwe."</string>
-    <string name="accessibility_recents_all_items_dismissed" msgid="5693205751863608046">"Zonke izinhlelo zokusebenza zakamuva zicashisiwe."</string>
-    <string name="accessibility_recents_item_open_app_info" msgid="3406797323476801016">"Vula ulwazi lohlelo lokusebenza le-<xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_item_launched" msgid="4519918148638221791">"Iqala i-<xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_empty_message" msgid="7967713254531861311">"Azikho izinto zakamuva"</string>
-    <string name="recents_empty_message_dismissed_all" msgid="1850214584987361375">"Usule yonke into"</string>
-    <string name="recents_app_info_button_label" msgid="8732926607391786762">"Ulwazi lohlelo lokusebenza"</string>
-    <string name="recents_lock_to_app_button_label" msgid="6087750201863853365">"ukuphina isikrini"</string>
-    <string name="recents_search_bar_label" msgid="638132045925945941">"sesha"</string>
-    <string name="recents_launch_error_message" msgid="9107963563503438012">"Ayikwazanga ukuqalisa i-<xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="826461671965217243">"I-<xliff:g id="APP">%s</xliff:g> ikhutshaziwe kumodi yokuphepha."</string>
-    <string name="recents_stack_action_button_label" msgid="1974273390109881497">"Sula konke"</string>
-    <string name="recents_drag_hint_message" msgid="610417221848280136">"Hudulela lapha ukuze usebenzise ukuhlukanisa kwesikrini"</string>
-    <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="488987777874979435">"Hlukanisa ngokuvundlile"</string>
-    <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="2498375296906391117">"Hlukanisa ngokumile"</string>
-    <string name="recents_multistack_add_stack_dialog_split_custom" msgid="7368405969130304811">"Hlukanisa ngokwezifiso"</string>
-    <string name="recents_accessibility_split_screen_top" msgid="8773505308411722524">"Hlukanisela isikrini phezulu"</string>
-    <string name="recents_accessibility_split_screen_left" msgid="722594718192007972">"Hlukanisela isikrini ngakwesokunxele"</string>
-    <string name="recents_accessibility_split_screen_right" msgid="2479764030969301514">"Hlukanisela isikrini ngakwesokudla"</string>
-</resources>
diff --git a/packages/SystemUI/legacy/recents/res/values/attrs.xml b/packages/SystemUI/legacy/recents/res/values/attrs.xml
deleted file mode 100644
index ef4cd5b..0000000
--- a/packages/SystemUI/legacy/recents/res/values/attrs.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-
-    <declare-styleable name="RecentsPanelView">
-        <attr name="recentItemLayout" format="reference" />
-        <!-- Style for the "Clear all" button. -->
-        <attr name="clearAllStyle" format="reference" />
-        <attr name="clearAllBackgroundColor" format="reference" />
-    </declare-styleable>
-
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values/colors.xml b/packages/SystemUI/legacy/recents/res/values/colors.xml
deleted file mode 100644
index 88b9b70..0000000
--- a/packages/SystemUI/legacy/recents/res/values/colors.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
-	<!-- The disabled recents task bar background color. -->
-    <color name="recents_task_bar_disabled_background_color">#ff676767</color>
-    <!-- The default recents task bar background color. -->
-    <color name="recents_task_bar_default_background_color">#ffe6e6e6</color>
-    <!-- The default recents task view background color. -->
-    <color name="recents_task_view_default_background_color">#fff3f3f3</color>
-    <!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
-    <color name="recents_task_bar_light_text_color">#ffeeeeee</color>
-    <!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_text_color">#cc000000</color>
-    <!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
-    <color name="recents_task_bar_light_icon_color">#ccffffff</color>
-    <!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
-    <color name="recents_task_bar_dark_icon_color">#99000000</color>
-    <!-- The lock to task button background color. -->
-    <color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
-    <!-- The lock to task button foreground color. -->
-    <color name="recents_task_view_lock_to_app_button_color">#ff666666</color>
-    <!-- The background color for the freeform workspace. -->
-    <color name="recents_freeform_workspace_bg_color">#33FFFFFF</color>
-
-    <!-- The background color for clear all button on light backgrounds if not transparent. -->
-    <color name="recents_clear_all_button_bg_light_color">#CCFFFFFF</color>
-    <!-- The background color for clear all button on dark backgrounds if not transparent. -->
-    <color name="recents_clear_all_button_bg_dark_color">#CC000000</color>
-
-    <!-- Shadow color for the first pixels around the fake shadow for recents. -->
-    <color name="fake_shadow_start_color">#44000000</color>
-
-    <!-- Shadow color for the furthest pixels around the fake shadow for recents. -->
-    <color name="fake_shadow_end_color">#03000000</color>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values/config.xml b/packages/SystemUI/legacy/recents/res/values/config.xml
deleted file mode 100644
index 2ff9abf..0000000
--- a/packages/SystemUI/legacy/recents/res/values/config.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2009, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds. -->
-<resources>
-
-    <!-- Component to be used as the recents implementation.  Must implement the
-     RecentsImplementation interface.  This name is in the ComponentName flattened format
-     (package/class)  -->
-    <string name="config_recentsComponent" translatable="false">com.android.systemui.recents.LegacyRecentsImpl</string>
-
-    <!-- Whether recents should use hardware layers for its taskviews. This flag can be enabled
-    for devices where the java drawing of round rects may be slow -->
-    <bool name="config_recents_use_hardware_layers">false</bool>
-
-    <!-- The number of app thumbnails we keep in memory -->
-    <integer name="config_recents_max_thumbnail_count">10</integer>
-
-    <!-- The number of app icons we keep in memory -->
-    <integer name="config_recents_max_icon_count">20</integer>
-
-    <!-- Whether to use cheap, less good looking shadows for recents -->
-    <bool name="config_recents_fake_shadows">false</bool>
-
-    <!-- The duration in seconds to wait before the dismiss buttons are shown. -->
-    <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
-
-    <!-- The duration for animating the task decorations in after transitioning from an app. -->
-    <integer name="recents_task_enter_from_app_duration">200</integer>
-
-    <!-- The duration for animating the task decorations in after transitioning from an app. -->
-    <integer name="recents_task_enter_from_affiliated_app_duration">125</integer>
-
-    <!-- The duration for animating the task decorations out before transitioning to an app. -->
-    <integer name="recents_task_exit_to_app_duration">125</integer>
-
-    <!-- The min animation duration for animating the nav bar scrim in. -->
-    <integer name="recents_nav_bar_scrim_enter_duration">400</integer>
-
-    <!-- The animation duration for scrolling the stack to a particular item. -->
-    <integer name="recents_animate_task_stack_scroll_duration">200</integer>
-
-    <!-- The delay to enforce between each alt-tab key press. -->
-    <integer name="recents_alt_tab_key_delay">200</integer>
-
-    <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
-    <integer name="recents_svelte_level">0</integer>
-
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is focused. -->
-    <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
-    <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>
-
-    <!-- Recents: The relative range of visible tasks from the current scroll position
-         while the stack is not focused. -->
-    <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
-    <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values/dimens.xml b/packages/SystemUI/legacy/recents/res/values/dimens.xml
deleted file mode 100644
index 528610e4..0000000
--- a/packages/SystemUI/legacy/recents/res/values/dimens.xml
+++ /dev/null
@@ -1,110 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2006, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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>
-<!-- Recents Layout -->
-
-    <!-- The amount to inset the stack, specifically at the top and the other sides.  We also
-         don't want this to change across configurations that Recents can be opened in, so we
-         define them statically for all display sizes. -->
-    <dimen name="recents_layout_min_margin">16dp</dimen>
-    <dimen name="recents_layout_top_margin_phone">16dp</dimen>
-    <dimen name="recents_layout_top_margin_tablet">32dp</dimen>
-    <dimen name="recents_layout_top_margin_tablet_xlarge">40dp</dimen>
-    <dimen name="recents_layout_bottom_margin">16dp</dimen>
-    <dimen name="recents_layout_side_margin_phone">16dp</dimen>
-    <dimen name="recents_layout_side_margin_tablet">48dp</dimen>
-    <dimen name="recents_layout_side_margin_tablet_docked">16dp</dimen>
-    <dimen name="recents_layout_side_margin_tablet_xlarge">64dp</dimen>
-    <dimen name="recents_layout_side_margin_tablet_xlarge_docked">16dp</dimen>
-
-    <!-- The height between the top margin and the top of the focused task. -->
-    <dimen name="recents_layout_top_peek_size">48dp</dimen>
-    <!-- The height between the bottom margin and the top of task in front of the focused task. -->
-    <dimen name="recents_layout_bottom_peek_size">56dp</dimen>
-
-    <!-- The offset from the top and bottom of the stack of the focused task.  The bottom offset
-         will be additionally offset by the bottom system insets since it goes under the nav bar
-         in certain orientations. -->
-    <dimen name="recents_layout_initial_top_offset_phone_port">128dp</dimen>
-    <dimen name="recents_layout_initial_bottom_offset_phone_port">80dp</dimen>
-    <dimen name="recents_layout_initial_top_offset_phone_land">72dp</dimen>
-    <dimen name="recents_layout_initial_bottom_offset_phone_land">72dp</dimen>
-    <dimen name="recents_layout_initial_top_offset_tablet">160dp</dimen>
-    <dimen name="recents_layout_initial_bottom_offset_tablet">112dp</dimen>
-
-    <!-- The min/max translationZ for the tasks in the stack. -->
-    <dimen name="recents_layout_z_min">3dp</dimen>
-    <dimen name="recents_layout_z_max">24dp</dimen>
-
-    <!-- The margin between the freeform and stack.  We also don't want this to change across
-         configurations that Recents can be opened in, so we define them statically for all
-         display sizes. -->
-    <dimen name="recents_freeform_layout_bottom_margin">16dp</dimen>
-
-    <!-- The padding between each freeform task. -->
-    <dimen name="recents_freeform_layout_task_padding">8dp</dimen>
-
-<!-- Recents Views -->
-
-    <!-- The height of a task view bar.  This has to be large enough to cover the action bar
-         height in either orientation at this smallest width. -->
-    <dimen name="recents_task_view_header_height">56dp</dimen>
-    <dimen name="recents_task_view_header_height_tablet_land">64dp</dimen>
-
-    <!-- The padding of a button in the recents task view header. -->
-    <dimen name="recents_task_view_header_button_padding">16dp</dimen>
-    <dimen name="recents_task_view_header_button_padding_tablet_land">20dp</dimen>
-
-    <!-- The radius of the rounded corners on a task view and its shadow (which can be larger
-         to create a softer corner effect. -->
-    <dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
-    <dimen name="recents_task_view_shadow_rounded_corners_radius">6dp</dimen>
-
-    <!-- The amount of highlight to make on each task view. -->
-    <dimen name="recents_task_view_highlight">1dp</dimen>
-
-    <!-- The size of the lock-to-app button and its icon. -->
-    <dimen name="recents_lock_to_app_size">56dp</dimen>
-    <dimen name="recents_lock_to_app_icon_size">28dp</dimen>
-
-    <!-- The amount of overscroll allowed when flinging to the end of the stack. -->
-    <dimen name="recents_fling_overscroll_distance">24dp</dimen>
-
-    <!-- The size of the drag hint text. -->
-    <dimen name="recents_drag_hint_text_size">14sp</dimen>
-
-    <!-- The min alpha to apply to a task affiliation group color. -->
-    <item name="recents_task_affiliation_color_min_alpha_percentage" format="float" type="dimen">0.6</item>
-
-    <!-- The amount to offset when animating into an affiliate group. -->
-    <dimen name="recents_task_stack_animation_affiliate_enter_offset">32dp</dimen>
-
-    <!-- The offsets the tasks animate from when recents is launched while docking -->
-    <dimen name="recents_task_stack_animation_launched_while_docking_offset">144dp</dimen>
-
-    <!-- The amount to translate when animating the removal of a task. -->
-    <dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
-
-    <!-- The alpha to apply to the recents row when it doesn't have focus -->
-    <item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
-
-    <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
-         loading full resolution screenshots. -->
-    <dimen name="recents_fast_fling_velocity">600dp</dimen>
-
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values/dimens_grid.xml b/packages/SystemUI/legacy/recents/res/values/dimens_grid.xml
deleted file mode 100644
index febf65b8..0000000
--- a/packages/SystemUI/legacy/recents/res/values/dimens_grid.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * 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.
-*/
--->
-<resources>
-  <dimen name="recents_grid_padding_left_right">32dp</dimen>
-  <dimen name="recents_grid_padding_top_bottom">150dp</dimen>
-  <dimen name="recents_grid_padding_task_view">20dp</dimen>
-  <dimen name="recents_grid_task_view_header_height">44dp</dimen>
-  <dimen name="recents_grid_task_view_header_button_padding">8dp</dimen>
-  <dimen name="recents_grid_task_view_focused_frame_thickness">8dp</dimen>
-  <dimen name="recents_grid_task_view_rounded_corners_radius">4dp</dimen>
-  <dimen name="recents_grid_task_view_focused_frame_rounded_corners_radius">8dp</dimen>
-</resources>
-
diff --git a/packages/SystemUI/legacy/recents/res/values/strings.xml b/packages/SystemUI/legacy/recents/res/values/strings.xml
deleted file mode 100644
index 4b44ba9..0000000
--- a/packages/SystemUI/legacy/recents/res/values/strings.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-	
-    <!-- Content description for the recent apps panel (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_desc_recent_apps">Overview.</string>
-
-    <!-- Content description to tell the user that this button will remove an application from recents -->
-    <string name="accessibility_recents_item_will_be_dismissed">Dismiss <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
-    <!-- Content description to tell the user an application has been removed from recents -->
-    <string name="accessibility_recents_item_dismissed"><xliff:g id="app" example="Calendar">%s</xliff:g> dismissed.</string>
-    <!-- Content description to tell the user all applications has been removed from recents -->
-    <string name="accessibility_recents_all_items_dismissed">All recent applications dismissed.</string>
-    <!-- Content description to tell the user that this button will open application info for an application in recents -->
-    <string name="accessibility_recents_item_open_app_info">Open <xliff:g id="app" example="Calendar">%s</xliff:g> application info.</string>
-    <!-- Content description to tell the user an application has been launched from recents -->
-    <string name="accessibility_recents_item_launched">Starting <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
-
-    <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
-    <string name="recents_empty_message">No recent items</string>
-    <!-- Recents: The empty recents string after dismissing all tasks. [CHAR LIMIT=NONE] -->
-    <string name="recents_empty_message_dismissed_all">You\'ve cleared everything</string>
-    <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
-    <string name="recents_app_info_button_label">Application Info</string>
-    <!-- Recents: The screen pinning button. [CHAR LIMIT=NONE] -->
-    <string name="recents_lock_to_app_button_label">screen pinning</string>
-    <!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] -->
-    <string name="recents_search_bar_label">search</string>
-    <!-- Recents: Launch error string. [CHAR LIMIT=NONE] -->
-    <string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
-    <!-- Recents: Launch disabled string. [CHAR LIMIT=NONE] -->
-    <string name="recents_launch_disabled_message"><xliff:g id="app" example="Calendar">%s</xliff:g> is disabled in safe-mode.</string>
-    <!-- Recents: Stack action button string. [CHAR LIMIT=NONE] -->
-    <string name="recents_stack_action_button_label">Clear all</string>
-    <!-- Recents: Hint text that shows on the drop targets to start multiwindow. [CHAR LIMIT=NONE] -->
-    <string name="recents_drag_hint_message">Drag here to use split screen</string>
-
-    <!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
-    <string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
-    <!-- Recents: MultiStack add stack split vertical radio button. [CHAR LIMIT=NONE] -->
-    <string name="recents_multistack_add_stack_dialog_split_vertical">Split Vertical</string>
-    <!-- Recents: MultiStack add stack split custom radio button. [CHAR LIMIT=NONE] -->
-    <string name="recents_multistack_add_stack_dialog_split_custom">Split Custom</string>
-    <!-- Recents: Accessibility split to the top -->
-    <string name="recents_accessibility_split_screen_top">Split screen to the top</string>
-    <!-- Recents: Accessibility split to the left -->
-    <string name="recents_accessibility_split_screen_left">Split screen to the left</string>
-    <!-- Recents: Accessibility split to the right -->
-    <string name="recents_accessibility_split_screen_right">Split screen to the right</string>
-
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/res/values/styles.xml b/packages/SystemUI/legacy/recents/res/values/styles.xml
deleted file mode 100644
index eb16be7..0000000
--- a/packages/SystemUI/legacy/recents/res/values/styles.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="RecentsTheme" parent="@android:style/Theme.Material">
-        <!-- NoTitle -->
-        <item name="android:windowNoTitle">true</item>
-        <!-- Misc -->
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
-        <item name="android:windowAnimationStyle">@null</item>
-        <item name="android:ambientShadowAlpha">0.35</item>
-    </style>
-
-    <!-- Recents theme -->
-    <style name="RecentsTheme.Wallpaper">
-        <item name="android:windowBackground">@*android:color/transparent</item>
-        <item name="android:colorBackgroundCacheHint">@null</item>
-        <item name="android:windowShowWallpaper">true</item>
-        <item name="android:windowDisablePreview">true</item>
-        <item name="clearAllStyle">@style/ClearAllButtonDefaultMargins</item>
-        <item name="clearAllBackgroundColor">@color/recents_clear_all_button_bg_dark_color</item>
-        <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
-        <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
-    </style>
-
-    <style name="RecentsTheme.Wallpaper.Light">
-        <item name="clearAllBackgroundColor">@color/recents_clear_all_button_bg_light_color</item>
-        <item name="wallpaperTextColor">@*android:color/primary_text_material_light</item>
-        <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
-    </style>
-
-    <!-- Performance optimized Recents theme (no wallpaper) -->
-    <style name="RecentsTheme.NoWallpaper">
-        <item name="android:windowBackground">@android:color/black</item>
-        <item name="wallpaperTextColor">@android:color/white</item>
-        <item name="wallpaperTextColorSecondary">@android:color/white</item>
-    </style>
-
-  </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/Constants.java
deleted file mode 100644
index 003379f..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/Constants.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-/**
- * Constants
- */
-public class Constants {
-
-    // TODO: Move into RecentsMetrics
-    public static class Metrics {
-        // DO NOT MODIFY THE ORDER OF THESE METRICS
-        public static final int DismissSourceKeyboard = 0;
-        public static final int DismissSourceSwipeGesture = 1;
-        public static final int DismissSourceHeaderButton = 2;
-        @Deprecated
-        public static final int DismissSourceHistorySwipeGesture = 3;
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
deleted file mode 100644
index 90c1099..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.graphics.Rect;
-
-/**
- * Due to the fact that RecentsActivity is per-user, we need to establish an
- * interface (this) for the system user to callback to the secondary users in
- * response to UI events coming in from the system user's SystemUI.
- */
-oneway interface IRecentsNonSystemUserCallbacks {
-    void preloadRecents();
-    void cancelPreloadingRecents();
-    void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate,
-            int recentsGrowTarget);
-    void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
-    void toggleRecents(int recentsGrowTarget);
-    void onConfigurationChanged();
-    void splitPrimaryTask(int topTaskId, int stackCreateMode, in Rect initialBounds);
-    void onDraggingInRecents(float distanceFromTop);
-    void onDraggingInRecentsEnded(float velocity);
-    void showCurrentUserToast(int msgResId, int msgLength);
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
deleted file mode 100644
index e977144..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.graphics.Rect;
-
-/**
- * Due to the fact that RecentsActivity is per-user, we need to establish an
- * interface (this) for the non-system user to register itself for callbacks and to
- * callback to the system user to update internal state.
- */
-oneway interface IRecentsSystemUserCallbacks {
-    void registerNonSystemUserCallbacks(IBinder nonSystemUserCallbacks, int userId);
-
-    void updateRecentsVisibility(boolean visible);
-    void startScreenPinning(int taskId);
-    void sendRecentsDrawnEvent();
-    void sendDockingTopTaskEvent(in Rect initialRect);
-    void sendLaunchRecentsEvent();
-    void sendDockedFirstAnimationFrameEvent();
-    void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart);
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/LegacyRecentsImpl.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/LegacyRecentsImpl.java
deleted file mode 100644
index a150de9..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/LegacyRecentsImpl.java
+++ /dev/null
@@ -1,750 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.EventLog;
-import android.util.Log;
-import android.view.Display;
-import android.widget.Toast;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.EventLogConstants;
-import com.android.systemui.EventLogTags;
-import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.pip.PipUI;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
-import com.android.systemui.recents.events.component.ExpandPipEvent;
-import com.android.systemui.recents.events.component.HidePipMenuEvent;
-import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.events.component.ShowUserToastEvent;
-import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
-import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.stackdivider.Divider;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * An implementation of the SystemUI recents component, which supports both system and secondary
- * users.
- */
-public class LegacyRecentsImpl implements RecentsImplementation {
-
-    private final static String TAG = "Recents";
-
-    public final static int EVENT_BUS_PRIORITY = 1;
-    public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
-
-    public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
-    static {
-        RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
-    }
-
-    private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
-    private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
-    private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
-
-    private static SystemServicesProxy sSystemServicesProxy;
-    private static RecentsDebugFlags sDebugFlags;
-    private static RecentsTaskLoader sTaskLoader;
-    private static RecentsConfiguration sConfiguration;
-
-    private Context mContext;
-    private SysUiServiceProvider mSysUiServiceProvider;
-    private Handler mHandler;
-    private RecentsImpl mImpl;
-
-    // Only For system user, this is the callbacks instance we return to each secondary user
-    private RecentsSystemUser mSystemToUserCallbacks;
-
-    // Only for secondary users, this is the callbacks instance provided by the system user to make
-    // calls back
-    private IRecentsSystemUserCallbacks mUserToSystemCallbacks;
-
-    // The set of runnables to run after binding to the system user's service.
-    private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
-
-    // Only for secondary users, this is the death handler for the binder from the system user
-    private final IBinder.DeathRecipient mUserToSystemCallbacksDeathRcpt = new IBinder.DeathRecipient() {
-        @Override
-        public void binderDied() {
-            mUserToSystemCallbacks = null;
-            EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
-                    EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_UNBOUND,
-                    sSystemServicesProxy.getProcessUser());
-
-            // Retry after a fixed duration
-            mHandler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    registerWithSystemUser();
-                }
-            }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
-        }
-    };
-
-    // Only for secondary users, this is the service connection we use to connect to the system user
-    private final ServiceConnection mUserToSystemServiceConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (service != null) {
-                mUserToSystemCallbacks = IRecentsSystemUserCallbacks.Stub.asInterface(
-                        service);
-                EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
-                        EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_SYSTEM_BOUND,
-                        sSystemServicesProxy.getProcessUser());
-
-                // Listen for system user's death, so that we can reconnect later
-                try {
-                    service.linkToDeath(mUserToSystemCallbacksDeathRcpt, 0);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Lost connection to (System) SystemUI", e);
-                }
-
-                // Run each of the queued runnables
-                runAndFlushOnConnectRunnables();
-            }
-
-            // Unbind ourselves now that we've registered our callbacks.  The
-            // binder to the system user are still valid at this point.
-            mContext.unbindService(this);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            // Do nothing
-        }
-    };
-
-    /**
-     * Returns the callbacks interface that non-system users can call.
-     */
-    public IBinder getSystemUserCallbacks() {
-        return mSystemToUserCallbacks;
-    }
-
-    public static RecentsTaskLoader getTaskLoader() {
-        return sTaskLoader;
-    }
-
-
-    public static SystemServicesProxy getSystemServices() {
-        return sSystemServicesProxy;
-    }
-
-    public static RecentsConfiguration getConfiguration() {
-        return sConfiguration;
-    }
-
-    public static RecentsDebugFlags getDebugFlags() {
-        return sDebugFlags;
-    }
-
-    @Override
-    public void onStart(Context context, SysUiServiceProvider sysUiServiceProvider) {
-        mContext = context;
-        mSysUiServiceProvider = sysUiServiceProvider;
-        final Resources res = mContext.getResources();
-        final int defaultTaskBarBackgroundColor =
-                mContext.getColor(R.color.recents_task_bar_default_background_color);
-        final int defaultTaskViewBackgroundColor =
-                mContext.getColor(R.color.recents_task_view_default_background_color);
-        sDebugFlags = new RecentsDebugFlags();
-        sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
-        sConfiguration = new RecentsConfiguration(mContext);
-        sTaskLoader = new RecentsTaskLoader(mContext,
-                // TODO: Once we start building the AAR, move these into the loader
-                res.getInteger(R.integer.config_recents_max_thumbnail_count),
-                res.getInteger(R.integer.config_recents_max_icon_count),
-                res.getInteger(R.integer.recents_svelte_level));
-        sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
-        mHandler = new Handler();
-        mImpl = new RecentsImpl(mContext);
-
-        // Register with the event bus
-        EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
-        EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
-        EventBus.getDefault().register(sTaskLoader, EVENT_BUS_PRIORITY);
-
-        // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
-        // the system user's Recents component to pass events (like show/hide/toggleRecents) to the
-        // secondary user, and vice versa (like visibility change, screen pinning).
-        final int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            // For the system user, initialize an instance of the interface that we can pass to the
-            // secondary user
-            mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl);
-        } else {
-            // For the secondary user, bind to the primary user's service to get a persistent
-            // interface to register its implementation and to later update its state
-            registerWithSystemUser();
-        }
-    }
-
-    @Override
-    public void onBootCompleted() {
-        mImpl.onBootCompleted();
-    }
-
-
-    @Override
-    public void growRecents() {
-        EventBus.getDefault().send(new RecentsGrowingEvent());
-    }
-
-    /**
-     * Shows the Recents.
-     */
-    @Override
-    public void showRecentApps(boolean triggeredFromAltTab) {
-        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
-        int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
-                    true /* animate */, recentsGrowTarget);
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
-                                true /* animate */, recentsGrowTarget);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    /**
-     * Hides the Recents.
-     */
-    @Override
-    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    /**
-     * Toggles the Recents activity.
-     */
-    @Override
-    public void toggleRecentApps() {
-        int growTarget = getComponent(Divider.class).getView().growsRecents();
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.toggleRecents(growTarget);
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.toggleRecents(growTarget);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    /**
-     * Preloads info for the Recents activity.
-     */
-    @Override
-    public void preloadRecentApps() {
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.preloadRecents();
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.preloadRecents();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    @Override
-    public void cancelPreloadRecentApps() {
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.cancelPreloadingRecents();
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.cancelPreloadingRecents();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds, int metricsDockAction) {
-        Point realSize = new Point();
-        if (initialBounds == null) {
-            mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
-                    .getRealSize(realSize);
-            initialBounds = new Rect(0, 0, realSize.x, realSize.y);
-        }
-
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        final int activityType = runningTask != null
-                ? runningTask.configuration.windowConfiguration.getActivityType()
-                : ACTIVITY_TYPE_UNDEFINED;
-        boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
-        boolean isRunningTaskInHomeOrRecentsStack =
-                activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
-        if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
-            logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
-            if (runningTask.supportsSplitScreenMultiWindow) {
-                if (metricsDockAction != -1) {
-                    MetricsLogger.action(mContext, metricsDockAction,
-                            runningTask.topActivity.flattenToShortString());
-                }
-                if (sSystemServicesProxy.isSystemUser(currentUser)) {
-                    mImpl.splitPrimaryTask(runningTask.id, stackCreateMode, initialBounds);
-                } else {
-                    if (mSystemToUserCallbacks != null) {
-                        IRecentsNonSystemUserCallbacks callbacks =
-                                mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                        if (callbacks != null) {
-                            try {
-                                callbacks.splitPrimaryTask(runningTask.id, stackCreateMode,
-                                        initialBounds);
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Callback failed", e);
-                            }
-                        } else {
-                            Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                        }
-                    }
-                }
-
-                return true;
-            } else {
-                EventBus.getDefault().send(new ShowUserToastEvent(
-                        R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT));
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    public static void logDockAttempt(Context ctx, ComponentName activity, int resizeMode) {
-        if (resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE) {
-            MetricsLogger.action(ctx, MetricsEvent.ACTION_WINDOW_DOCK_UNRESIZABLE,
-                    activity.flattenToShortString());
-        }
-        MetricsLogger.count(ctx, getMetricsCounterForResizeMode(resizeMode), 1);
-    }
-
-    private static String getMetricsCounterForResizeMode(int resizeMode) {
-        switch (resizeMode) {
-            case ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE:
-                return COUNTER_WINDOW_UNSUPPORTED;
-            case ActivityInfo.RESIZE_MODE_RESIZEABLE:
-            case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
-                return COUNTER_WINDOW_SUPPORTED;
-            default:
-                return COUNTER_WINDOW_INCOMPATIBLE;
-        }
-    }
-
-    @Override
-    public void onAppTransitionFinished() {
-        if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            // Fallback, reset the flag once an app transition ends
-            EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(
-                    false /* waitingForTransitionStart */));
-        }
-    }
-
-    /**
-     * Updates on configuration change.
-     */
-    public void onConfigurationChanged(Configuration newConfig) {
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.onConfigurationChanged();
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.onConfigurationChanged();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    /**
-     * Handle Recents activity visibility changed.
-     */
-    public final void onBusEvent(final RecentsVisibilityChangedEvent event) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        int processUser = ssp.getProcessUser();
-        if (ssp.isSystemUser(processUser)) {
-            mImpl.onVisibilityChanged(event.applicationContext, event.visible);
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.updateRecentsVisibility(event.visible);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-
-        // This will catch the cases when a user launches from recents to another app
-        // (and vice versa) that is not in the recents stack (such as home or bugreport) and it
-        // would not reset the wait for transition flag. This will catch it and make sure that the
-        // flag is reset.
-        if (!event.visible) {
-            mImpl.setWaitingForTransitionStart(false);
-        }
-    }
-
-    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        int processUser = ssp.getProcessUser();
-        if (ssp.isSystemUser(processUser)) {
-            final Divider divider = getComponent(Divider.class);
-            if (divider != null) {
-                divider.onDockedFirstAnimationFrame();
-            }
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    /**
-     * Handle screen pinning request.
-     */
-    public final void onBusEvent(final ScreenPinningRequestEvent event) {
-        int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            mImpl.onStartScreenPinning(event.applicationContext, event.taskId);
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.startScreenPinning(event.taskId);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(final RecentsDrawnEvent event) {
-        int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            final Divider divider = getComponent(Divider.class);
-            if (divider != null) {
-                divider.onRecentsDrawn();
-            }
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.sendRecentsDrawnEvent();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(final DockedTopTaskEvent event) {
-        int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            final Divider divider = getComponent(Divider.class);
-            if (divider != null) {
-                divider.onDockedTopTask();
-            }
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.sendDockingTopTaskEvent(event.initialRect);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(final RecentsActivityStartingEvent event) {
-        int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            final Divider divider = getComponent(Divider.class);
-            if (divider != null) {
-                divider.onRecentsActivityStarting();
-            }
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.sendLaunchRecentsEvent();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(LaunchTaskFailedEvent event) {
-        // Reset the transition when tasks fail to launch
-        mImpl.setWaitingForTransitionStart(false);
-    }
-
-    public final void onBusEvent(ConfigurationChangedEvent event) {
-        // Update the configuration for the Recents component when the activity configuration
-        // changes as well
-        mImpl.onConfigurationChanged();
-    }
-
-    public final void onBusEvent(ShowUserToastEvent event) {
-        int currentUser = sSystemServicesProxy.getCurrentUser();
-        if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.onShowCurrentUserToast(event.msgResId, event.msgLength);
-        } else {
-            if (mSystemToUserCallbacks != null) {
-                IRecentsNonSystemUserCallbacks callbacks =
-                        mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
-                if (callbacks != null) {
-                    try {
-                        callbacks.showCurrentUserToast(event.msgResId, event.msgLength);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                } else {
-                    Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
-                }
-            }
-        }
-    }
-
-    public final void onBusEvent(SetWaitingForTransitionStartEvent event) {
-        int processUser = sSystemServicesProxy.getProcessUser();
-        if (sSystemServicesProxy.isSystemUser(processUser)) {
-            mImpl.setWaitingForTransitionStart(event.waitingForTransitionStart);
-        } else {
-            postToSystemUser(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        mUserToSystemCallbacks.setWaitingForTransitionStartEvent(
-                                event.waitingForTransitionStart);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Callback failed", e);
-                    }
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(ExpandPipEvent event) {
-        PipUI pipUi = getComponent(PipUI.class);
-        if (pipUi == null) {
-            return;
-        }
-        pipUi.expandPip();
-    }
-
-    public final void onBusEvent(HidePipMenuEvent event) {
-        PipUI pipUi = getComponent(PipUI.class);
-        if (pipUi == null) {
-            return;
-        }
-        event.getAnimationTrigger().increment();
-        pipUi.hidePipMenu(() -> {
-                event.getAnimationTrigger().increment();
-            }, () -> {
-                event.getAnimationTrigger().decrement();
-            });
-        event.getAnimationTrigger().decrement();
-    }
-
-    /**
-     * Attempts to register with the system user.
-     */
-    private void registerWithSystemUser() {
-        final int processUser = sSystemServicesProxy.getProcessUser();
-        postToSystemUser(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    mUserToSystemCallbacks.registerNonSystemUserCallbacks(
-                            new RecentsImplProxy(mImpl), processUser);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to register", e);
-                }
-            }
-        });
-    }
-
-    /**
-     * Runs the runnable in the system user's Recents context, connecting to the service if
-     * necessary.
-     */
-    private void postToSystemUser(final Runnable onConnectRunnable) {
-        mOnConnectRunnables.add(onConnectRunnable);
-        if (mUserToSystemCallbacks == null) {
-            Intent systemUserServiceIntent = new Intent();
-            systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
-            boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
-                    mUserToSystemServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
-            EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
-                    EventLogConstants.SYSUI_RECENTS_CONNECTION_USER_BIND_SERVICE,
-                    sSystemServicesProxy.getProcessUser());
-            if (!bound) {
-                // Retry after a fixed duration
-                mHandler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        registerWithSystemUser();
-                    }
-                }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
-            }
-        } else {
-            runAndFlushOnConnectRunnables();
-        }
-    }
-
-    /**
-     * Runs all the queued runnables after a service connection is made.
-     */
-    private void runAndFlushOnConnectRunnables() {
-        for (Runnable r : mOnConnectRunnables) {
-            r.run();
-        }
-        mOnConnectRunnables.clear();
-    }
-
-    private <T> T getComponent(Class<T> clazz) {
-        return mSysUiServiceProvider.getComponent(clazz);
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        pw.println("Recents");
-        pw.println("  currentUserId=" + SystemServicesProxy.getInstance(mContext).getCurrentUser());
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java
deleted file mode 100644
index a7ccc3a..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivity.java
+++ /dev/null
@@ -1,858 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
-
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.app.TaskStackBuilder;
-import android.app.WallpaperManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
-import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
-import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
-import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
-import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
-import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
-import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
-import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
-import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
-import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UserInteractionEvent;
-import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.recents.views.RecentsView;
-import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * The main Recents activity that is started from RecentsComponent.
- */
-public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener,
-        ColorExtractor.OnColorsChangedListener {
-
-    private final static String TAG = "RecentsActivity";
-    private final static boolean DEBUG = false;
-
-    public final static int EVENT_BUS_PRIORITY = LegacyRecentsImpl.EVENT_BUS_PRIORITY + 1;
-    public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
-
-    private PackageMonitor mPackageMonitor = new PackageMonitor() {
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
-            }
-
-            @Override
-            public boolean onPackageChanged(String packageName, int uid, String[] components) {
-                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
-                return true;
-            }
-
-            @Override
-            public void onPackageModified(String packageName) {
-                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
-            }
-        };
-    private Handler mHandler = new Handler();
-    private long mLastTabKeyEventTime;
-    private boolean mFinishedOnStartup;
-    private boolean mIgnoreAltTabRelease;
-    private boolean mIsVisible;
-    private boolean mRecentsStartRequested;
-    private Configuration mLastConfig;
-
-    // Top level views
-    private RecentsView mRecentsView;
-    private SystemBarScrimViews mScrimViews;
-    private View mIncompatibleAppOverlay;
-
-    // Runnables to finish the Recents activity
-    private Intent mHomeIntent;
-
-    // The trigger to automatically launch the current task
-    private int mFocusTimerDuration;
-    private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
-
-    // Theme and colors
-    private SysuiColorExtractor mColorExtractor;
-    private boolean mUsingDarkText;
-
-    /**
-     * A common Runnable to finish Recents by launching Home with an animation depending on the
-     * last activity launch state. Generally we always launch home when we exit Recents rather than
-     * just finishing the activity since we don't know what is behind Recents in the task stack.
-     */
-    class LaunchHomeRunnable implements Runnable {
-
-        Intent mLaunchIntent;
-        ActivityOptions mOpts;
-
-        /**
-         * Creates a finish runnable that starts the specified intent.
-         */
-        public LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts) {
-            mLaunchIntent = launchIntent;
-            mOpts = opts;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mHandler.post(() -> {
-                    ActivityOptions opts = mOpts;
-                    if (opts == null) {
-                        opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this,
-                                R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit);
-                    }
-                    startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
-                });
-            } catch (Exception e) {
-                Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
-            }
-        }
-    }
-
-    /**
-     * Broadcast receiver to handle messages from the system
-     */
-    final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context ctx, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                // When the screen turns off, dismiss Recents to Home
-                dismissRecentsToHomeIfVisible(false);
-            } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
-                // When switching users, dismiss Recents to Home similar to screen off
-                finish();
-            }
-        }
-    };
-
-    private final OnPreDrawListener mRecentsDrawnEventListener =
-            new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
-                    EventBus.getDefault().post(new RecentsDrawnEvent());
-                    if (LatencyTracker.isEnabled(getApplicationContext())) {
-                        DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(
-                                getApplicationContext()).onActionEnd(
-                                LatencyTracker.ACTION_TOGGLE_RECENTS));
-                    }
-                    DejankUtils.postAfterTraversal(() -> {
-                        LegacyRecentsImpl.getTaskLoader().startLoader(RecentsActivity.this);
-                        LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
-                    });
-                    return true;
-                }
-            };
-
-    /**
-     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
-     */
-    boolean dismissRecentsToFocusedTask(int logCategory) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp.isRecentsActivityVisible()) {
-            // If we have a focused Task, launch that Task now
-            if (mRecentsView.launchFocusedTask(logCategory)) return true;
-        }
-        return false;
-    }
-
-    /**
-     * Dismisses recents back to the launch target task.
-     */
-    boolean dismissRecentsToLaunchTargetTaskOrHome() {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp.isRecentsActivityVisible()) {
-            // If we have a focused Task, launch that Task now
-            if (mRecentsView.launchPreviousTask()) return true;
-            // If none of the other cases apply, then just go Home
-            dismissRecentsToHome(true /* animateTaskViews */);
-        }
-        return false;
-    }
-
-    /**
-     * Dismisses recents if we are already visible and the intent is to toggle the recents view.
-     */
-    boolean dismissRecentsToFocusedTaskOrHome() {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp.isRecentsActivityVisible()) {
-            // If we have a focused Task, launch that Task now
-            if (mRecentsView.launchFocusedTask(0 /* logCategory */)) return true;
-            // If none of the other cases apply, then just go Home
-            dismissRecentsToHome(true /* animateTaskViews */);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Dismisses Recents directly to Home without checking whether it is currently visible.
-     */
-    void dismissRecentsToHome(boolean animateTaskViews) {
-        dismissRecentsToHome(animateTaskViews, null);
-    }
-
-    /**
-     * Dismisses Recents directly to Home without checking whether it is currently visible.
-     *
-     * @param overrideAnimation If not null, will override the default animation that is based on
-     *                          how Recents was launched.
-     */
-    void dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation) {
-        DismissRecentsToHomeAnimationStarted dismissEvent =
-                new DismissRecentsToHomeAnimationStarted(animateTaskViews);
-        dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
-                overrideAnimation));
-        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
-        EventBus.getDefault().send(dismissEvent);
-    }
-
-    /** Dismisses Recents directly to Home if we currently aren't transitioning. */
-    boolean dismissRecentsToHomeIfVisible(boolean animated) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp.isRecentsActivityVisible()) {
-            // Return to Home
-            dismissRecentsToHome(animated);
-            return true;
-        }
-        return false;
-    }
-
-    /** Called with the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mFinishedOnStartup = false;
-
-        // In the case that the activity starts up before the Recents component has initialized
-        // (usually when debugging/pushing the SysUI apk), just finish this activity.
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp == null) {
-            mFinishedOnStartup = true;
-            finish();
-            return;
-        }
-
-        // Register this activity with the event bus
-        EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
-
-        // Initialize the package monitor
-        mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
-                true /* externalStorage */);
-
-        // Select theme based on wallpaper colors
-        mColorExtractor = Dependency.get(SysuiColorExtractor.class);
-        mColorExtractor.addOnColorsChangedListener(this);
-        mUsingDarkText = mColorExtractor.getColors(ColorExtractor.TYPE_DARK,
-                WallpaperManager.FLAG_SYSTEM).supportsDarkText();
-        setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light
-                : R.style.RecentsTheme_Wallpaper);
-
-        // Set the Recents layout
-        setContentView(R.layout.recents);
-        takeKeyEvents(true);
-        mRecentsView = findViewById(R.id.recents_view);
-        mScrimViews = new SystemBarScrimViews(this);
-        getWindow().getAttributes().privateFlags |=
-                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
-        }
-
-        mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
-
-        // Set the window background
-        mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
-
-        // Create the home intent runnable
-        mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
-        mHomeIntent.addCategory(Intent.CATEGORY_HOME);
-        mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
-        // Register the broadcast receiver to handle messages when the screen is turned off
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        registerReceiver(mSystemBroadcastReceiver, filter);
-
-        getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-
-        // Reload the stack view whenever we are made visible again
-        reloadStackView();
-
-        // Notify that recents is now visible
-        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
-        MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY);
-
-        // Getting system scrim colors ignoring wallpaper visibility since it should never be grey.
-        ColorExtractor.GradientColors systemColors = mColorExtractor.getNeutralColors();
-        // We don't want to interpolate colors because we're defining the initial state.
-        // Gradient should be set/ready when you open "Recents".
-        mRecentsView.setScrimColors(systemColors, false);
-
-        // Notify of the next draw
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
-
-        // If Recents was restarted, then it should complete the enter animation with partially
-        // reset launch state with dock, app and home set to false
-        Object isRelaunching = getLastNonConfigurationInstance();
-        if (isRelaunching != null && isRelaunching instanceof Boolean && (boolean) isRelaunching) {
-            RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-            launchState.launchedViaDockGesture = false;
-            launchState.launchedFromApp = false;
-            launchState.launchedFromHome = false;
-            onEnterAnimationComplete();
-        }
-        mRecentsStartRequested = false;
-    }
-
-    @Override
-    public void onColorsChanged(ColorExtractor colorExtractor, int which) {
-        if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-            ColorExtractor.GradientColors colors = mColorExtractor.getNeutralColors();
-            boolean darkText = colors.supportsDarkText();
-            if (darkText != mUsingDarkText) {
-                mUsingDarkText = darkText;
-                setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light
-                        : R.style.RecentsTheme_Wallpaper);
-                mRecentsView.reevaluateStyles();
-            }
-            mRecentsView.setScrimColors(colors, true /* animated */);
-        }
-    }
-
-    /**
-     * Reloads the stack views upon launching Recents.
-     */
-    private void reloadStackView() {
-        // If the Recents component has preloaded a load plan, then use that to prevent
-        // reconstructing the task stack
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
-        if (loadPlan == null) {
-            loadPlan = new RecentsTaskLoadPlan(this);
-        }
-
-        // Start loading tasks according to the load plan
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        if (!loadPlan.hasTasks()) {
-            loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
-        }
-
-        RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
-        loadOpts.runningTaskId = launchState.launchedToTaskId;
-        loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
-        loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(loadPlan, loadOpts);
-        TaskStack stack = loadPlan.getTaskStack();
-        mRecentsView.onReload(stack, mIsVisible);
-
-        // Update the nav bar scrim, but defer the animation until the enter-window event
-        boolean animateNavBarScrim = !launchState.launchedViaDockGesture;
-        mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null);
-
-        // If this is a new instance relaunched by AM, without going through the normal mechanisms,
-        // then we have to manually trigger the enter animation state
-        boolean wasLaunchedByAm = !launchState.launchedFromHome &&
-                !launchState.launchedFromApp;
-        if (wasLaunchedByAm) {
-            EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
-        }
-
-        // Keep track of whether we launched from the nav bar button or via alt-tab
-        if (launchState.launchedWithAltTab) {
-            MetricsLogger.count(this, "overview_trigger_alttab", 1);
-        } else {
-            MetricsLogger.count(this, "overview_trigger_nav_btn", 1);
-        }
-
-        // Keep track of whether we launched from an app or from home
-        if (launchState.launchedFromApp) {
-            Task launchTarget = stack.getLaunchTarget();
-            int launchTaskIndexInStack = launchTarget != null
-                    ? stack.indexOfTask(launchTarget)
-                    : 0;
-            MetricsLogger.count(this, "overview_source_app", 1);
-            // If from an app, track the stack index of the app in the stack (for affiliated tasks)
-            MetricsLogger.histogram(this, "overview_source_app_index", launchTaskIndexInStack);
-        } else {
-            MetricsLogger.count(this, "overview_source_home", 1);
-        }
-
-        // Keep track of the total stack task count
-        int taskCount = mRecentsView.getStack().getTaskCount();
-        MetricsLogger.histogram(this, "overview_task_count", taskCount);
-
-        // After we have resumed, set the visible state until the next onStop() call
-        mIsVisible = true;
-    }
-
-    @Override
-    public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
-
-        // Workaround for b/64694148: The animation started callback is not made (see
-        // RecentsImpl.getThumbnailTransitionActivityOptions) so reset the transition-waiting state
-        // once the enter animation has completed.
-        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-    }
-
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        return true;
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        mIgnoreAltTabRelease = false;
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        // Notify of the config change
-        Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
-        int numStackTasks = mRecentsView.getStack().getTaskCount();
-        EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
-                mLastConfig.orientation != newDeviceConfiguration.orientation,
-                mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0));
-
-        mLastConfig.updateFrom(newDeviceConfiguration);
-    }
-
-    @Override
-    public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
-        super.onMultiWindowModeChanged(isInMultiWindowMode);
-
-        // Set the window background
-        mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode);
-
-        // Reload the task stack view if we are still visible to pick up the change in tasks that
-        // result from entering/exiting multi-window
-        if (mIsVisible) {
-            reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // Notify that recents is now hidden
-        mIsVisible = false;
-        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
-        MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
-        LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
-
-        // When recents starts again before onStop, do not reset launch flags so entrance animation
-        // can run
-        if (!isChangingConfigurations() && !mRecentsStartRequested) {
-            // Workaround for b/22542869, if the RecentsActivity is started again, but without going
-            // through SystemUI, we need to reset the config launch flags to ensure that we do not
-            // wait on the system to send a signal that was never queued.
-            RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-            RecentsActivityLaunchState launchState = config.getLaunchState();
-            launchState.reset();
-        }
-
-        // Force a gc to attempt to clean up bitmap references more quickly (b/38258699)
-        LegacyRecentsImpl.getSystemServices().gc();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // In the case that the activity finished on startup, just skip the unregistration below
-        if (mFinishedOnStartup) {
-            return;
-        }
-
-        // Unregister the system broadcast receivers
-        unregisterReceiver(mSystemBroadcastReceiver);
-
-        // Unregister any broadcast receivers for the task loader
-        mPackageMonitor.unregister();
-
-        EventBus.getDefault().unregister(this);
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY);
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        EventBus.getDefault().unregister(mScrimViews);
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        if (loader != null) {
-            loader.onTrimMemory(level);
-        }
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_TAB: {
-                int altTabKeyDelay = getResources().getInteger(R.integer.recents_alt_tab_key_delay);
-                boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
-                        mLastTabKeyEventTime) > altTabKeyDelay;
-                if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
-                    // Focus the next task in the stack
-                    final boolean backward = event.isShiftPressed();
-                    if (backward) {
-                        EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
-                    } else {
-                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
-                    }
-                    mLastTabKeyEventTime = SystemClock.elapsedRealtime();
-
-                    // In the case of another ALT event, don't ignore the next release
-                    if (event.isAltPressed()) {
-                        mIgnoreAltTabRelease = false;
-                    }
-                }
-                return true;
-            }
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_DPAD_RIGHT: {
-                final Direction direction = NavigateTaskViewEvent.getDirectionFromKeyCode(keyCode);
-                EventBus.getDefault().send(new NavigateTaskViewEvent(direction));
-                return true;
-            }
-            case KeyEvent.KEYCODE_DEL:
-            case KeyEvent.KEYCODE_FORWARD_DEL: {
-                if (event.getRepeatCount() <= 0) {
-                    EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
-
-                    // Keep track of deletions by keyboard
-                    MetricsLogger.histogram(this, "overview_task_dismissed_source",
-                            Constants.Metrics.DismissSourceKeyboard);
-                    return true;
-                }
-            }
-            default:
-                break;
-        }
-        return super.onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public void onUserInteraction() {
-        EventBus.getDefault().send(mUserInteractionEvent);
-    }
-
-    @Override
-    public void onBackPressed() {
-        // Back behaves like the recents button so just trigger a toggle event
-        EventBus.getDefault().send(new ToggleRecentsEvent());
-    }
-
-    /**** EventBus events ****/
-
-    public final void onBusEvent(ToggleRecentsEvent event) {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-        if (launchState.launchedFromHome) {
-            dismissRecentsToHome(true /* animateTaskViews */);
-        } else {
-            dismissRecentsToLaunchTargetTaskOrHome();
-        }
-    }
-
-    public final void onBusEvent(RecentsActivityStartingEvent event) {
-        mRecentsStartRequested = true;
-    }
-
-    public final void onBusEvent(HideRecentsEvent event) {
-        if (event.triggeredFromAltTab) {
-            // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
-            if (!mIgnoreAltTabRelease) {
-                dismissRecentsToFocusedTaskOrHome();
-            }
-        } else if (event.triggeredFromHomeKey) {
-            dismissRecentsToHome(true /* animateTaskViews */);
-
-            // Cancel any pending dozes
-            EventBus.getDefault().send(mUserInteractionEvent);
-        } else {
-            // Do nothing
-        }
-    }
-
-    public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
-        mRecentsView.invalidate();
-    }
-
-    public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
-        mRecentsView.invalidate();
-    }
-
-    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
-        mRecentsView.invalidate();
-    }
-
-    public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-        int launchToTaskId = launchState.launchedToTaskId;
-        if (launchToTaskId != -1 &&
-                (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
-            ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
-            am.cancelWindowTransition(launchState.launchedToTaskId);
-        }
-    }
-
-    public final void onBusEvent(ShowApplicationInfoEvent event) {
-        // Create a new task stack with the application info details activity
-        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null));
-        intent.setComponent(intent.resolveActivity(getPackageManager()));
-        TaskStackBuilder.create(this)
-                .addNextIntentWithParentStack(intent).startActivities(null,
-                        new UserHandle(event.task.key.userId));
-
-        // Keep track of app-info invocations
-        MetricsLogger.count(this, "overview_app_info", 1);
-    }
-
-    public final void onBusEvent(ShowIncompatibleAppOverlayEvent event) {
-        if (mIncompatibleAppOverlay == null) {
-            mIncompatibleAppOverlay = Utilities.findViewStubById(this,
-                    R.id.incompatible_app_overlay_stub).inflate();
-            mIncompatibleAppOverlay.setWillNotDraw(false);
-            mIncompatibleAppOverlay.setVisibility(View.VISIBLE);
-        }
-        mIncompatibleAppOverlay.animate()
-                .alpha(1f)
-                .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION)
-                .setInterpolator(Interpolators.ALPHA_IN)
-                .start();
-    }
-
-    public final void onBusEvent(HideIncompatibleAppOverlayEvent event) {
-        if (mIncompatibleAppOverlay != null) {
-            mIncompatibleAppOverlay.animate()
-                    .alpha(0f)
-                    .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .start();
-        }
-    }
-
-    public final void onBusEvent(DeleteTaskDataEvent event) {
-        // Remove any stored data from the loader
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        loader.deleteTaskData(event.task, false);
-
-        // Remove the task from activity manager
-        ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
-    }
-
-    public final void onBusEvent(TaskViewDismissedEvent event) {
-        mRecentsView.updateScrimOpacity();
-    }
-
-    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (ssp.hasDockedTask()) {
-            mRecentsView.showEmptyView(event.msgResId);
-        } else {
-            // Just go straight home (no animation necessary because there are no more task views)
-            dismissRecentsToHome(false /* animateTaskViews */);
-        }
-
-        // Keep track of all-deletions
-        MetricsLogger.count(this, "overview_task_all_dismissed", 1);
-    }
-
-    public final void onBusEvent(LaunchTaskSucceededEvent event) {
-        MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
-    }
-
-    public final void onBusEvent(LaunchTaskFailedEvent event) {
-        // Return to Home
-        dismissRecentsToHome(true /* animateTaskViews */);
-
-        MetricsLogger.count(this, "overview_task_launch_failed", 1);
-    }
-
-    public final void onBusEvent(ScreenPinningRequestEvent event) {
-        MetricsLogger.count(this, "overview_screen_pinned", 1);
-    }
-
-    public final void onBusEvent(StackViewScrolledEvent event) {
-        // Once the user has scrolled while holding alt-tab, then we should ignore the release of
-        // the key
-        mIgnoreAltTabRelease = true;
-    }
-
-    public final void onBusEvent(final DockedTopTaskEvent event) {
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
-        mRecentsView.invalidate();
-    }
-
-    public final void onBusEvent(final ActivityUnpinnedEvent event) {
-        if (mIsVisible) {
-            // Skip the configuration change event as the PiP activity does not actually affect the
-            // config of recents
-            reloadTaskStack(isInMultiWindowMode(), false /* sendConfigChangedEvent */);
-        }
-    }
-
-    private void reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent) {
-        // Reload the task stack completely
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
-        loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
-
-        RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
-        loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
-        loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(loadPlan, loadOpts);
-
-        TaskStack stack = loadPlan.getTaskStack();
-        int numStackTasks = stack.getTaskCount();
-        boolean showDeferredAnimation = numStackTasks > 0;
-
-        if (sendConfigChangedEvent) {
-            EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
-                    false /* fromDeviceOrientationChange */, false /* fromDisplayDensityChange */,
-                    numStackTasks > 0));
-        }
-        EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode,
-                showDeferredAnimation, stack));
-    }
-
-    @Override
-    public boolean onPreDraw() {
-        mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
-        return true;
-    }
-
-    public void onPackageChanged(String packageName, int userId) {
-        LegacyRecentsImpl.getTaskLoader().onPackageChanged(packageName);
-        EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
-    }
-
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        super.dump(prefix, fd, writer, args);
-        EventBus.getDefault().dump(prefix, writer);
-        LegacyRecentsImpl.getTaskLoader().dump(prefix, writer);
-
-        String id = Integer.toHexString(System.identityHashCode(this));
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
-        writer.print(" currentTime="); writer.print(System.currentTimeMillis());
-        writer.print(" [0x"); writer.print(id); writer.print("]");
-        writer.println();
-
-        if (mRecentsView != null) {
-            mRecentsView.dump(prefix, writer);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivityLaunchState.java
deleted file mode 100644
index 14fda95..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-/**
- * The launch state of the RecentsActivity.
- *
- * Current Constraints:
- *  - needed in onStart() before onNewIntent()
- *  - needs to be reset when Recents is hidden
- *  - needs to be computed in Recents component
- *  - needs to be accessible by views
- */
-public class RecentsActivityLaunchState {
-
-    public boolean launchedWithAltTab;
-    public boolean launchedFromApp;
-    // Set if the activity that we launched from entered PiP during the transition into Recents
-    public boolean launchedFromPipApp;
-    // Set if the next activity that quick-switch will launch is the PiP activity
-    public boolean launchedWithNextPipApp;
-    public boolean launchedFromHome;
-    public boolean launchedViaDragGesture;
-    public boolean launchedViaDockGesture;
-    public int launchedToTaskId;
-    public int launchedNumVisibleTasks;
-    public int launchedNumVisibleThumbnails;
-
-    public void reset() {
-        launchedFromHome = false;
-        launchedFromApp = false;
-        launchedFromPipApp = false;
-        launchedWithNextPipApp = false;
-        launchedToTaskId = -1;
-        launchedWithAltTab = false;
-        launchedViaDragGesture = false;
-        launchedViaDockGesture = false;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsConfiguration.java
deleted file mode 100644
index ee53734..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsConfiguration.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-
-import android.os.SystemProperties;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.views.DockState;
-
-/**
- * Represents the dock regions for each orientation.
- */
-class DockRegion {
-    public static DockState[] PHONE_LANDSCAPE = {
-            // We only allow docking to the left in landscape for now on small devices
-            DockState.LEFT
-    };
-    public static DockState[] PHONE_PORTRAIT = {
-            // We only allow docking to the top for now on small devices
-            DockState.TOP
-    };
-    public static DockState[] TABLET_LANDSCAPE = {
-            DockState.LEFT,
-            DockState.RIGHT
-    };
-    public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
-}
-
-/**
- * Application resources that can be retrieved from the application context and are not specifically
- * tied to the current activity.
- */
-public class RecentsConfiguration {
-
-    private static final int LARGE_SCREEN_MIN_DP = 600;
-    private static final int XLARGE_SCREEN_MIN_DP = 720;
-
-    // Launch states
-    public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
-
-    // Since the positions in Recents has to be calculated globally (before the RecentsActivity
-    // starts), we need to calculate some resource values ourselves, instead of relying on framework
-    // resources.
-    public final boolean isLargeScreen;
-    public final boolean isXLargeScreen;
-    public final int smallestWidth;
-
-    /** Misc **/
-    public boolean fakeShadows;
-    public int svelteLevel;
-
-    // Whether this product supports Grid-based Recents. If this is field is set to true, then
-    // Recents will layout task views in a grid mode when there's enough space in the screen.
-    public boolean isGridEnabled;
-
-    // Support for Android Recents for low ram devices. If this field is set to true, then Recents
-    // will use the alternative layout.
-    public boolean isLowRamDevice;
-
-    // Enable drag and drop split from Recents. Disabled for low ram devices.
-    public boolean dragToSplitEnabled;
-
-    private final Context mAppContext;
-
-    public RecentsConfiguration(Context context) {
-        // Load only resources that can not change after the first load either through developer
-        // settings or via multi window
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        mAppContext = context.getApplicationContext();
-        Resources res = mAppContext.getResources();
-        fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
-        svelteLevel = res.getInteger(R.integer.recents_svelte_level);
-        isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
-        isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
-        dragToSplitEnabled = !isLowRamDevice;
-
-        float screenDensity = context.getResources().getDisplayMetrics().density;
-        smallestWidth = ssp.getDeviceSmallestWidth();
-        isLargeScreen = smallestWidth >= (int) (screenDensity * LARGE_SCREEN_MIN_DP);
-        isXLargeScreen = smallestWidth >= (int) (screenDensity * XLARGE_SCREEN_MIN_DP);
-    }
-
-    /**
-     * Returns the activity launch state.
-     * TODO: This will be refactored out of RecentsConfiguration.
-     */
-    public RecentsActivityLaunchState getLaunchState() {
-        return mLaunchState;
-    }
-
-    /**
-     * Returns the preferred dock states for the current orientation.
-     * @return a list of dock states for device and its orientation
-     */
-    public DockState[] getDockStatesForCurrentOrientation() {
-        boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
-                Configuration.ORIENTATION_LANDSCAPE;
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        if (config.isLargeScreen) {
-            return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT;
-        } else {
-            return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT;
-        }
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsDebugFlags.java
deleted file mode 100644
index 1918593..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-public class RecentsDebugFlags {
-
-    public static class Static {
-        // Enables debug drawing for the transition thumbnail
-        public static final boolean EnableTransitionThumbnailDebugMode = false;
-
-        // Disables enter and exit transitions for other tasks for low ram devices
-        public static final boolean DisableRecentsLowRamEnterExitAnimation = false;
-
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImpl.java
deleted file mode 100644
index 3e5acab..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImpl.java
+++ /dev/null
@@ -1,1118 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.view.View.MeasureSpec;
-
-import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.trust.TrustManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentCallbacks2;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.MutableBoolean;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
-
-import android.widget.Toast;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.pip.phone.ForegroundThread;
-import com.google.android.collect.Lists;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.R;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
-import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
-import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
-import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
-import com.android.systemui.recents.events.component.ActivityPinnedEvent;
-import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
-import com.android.systemui.recents.events.component.HidePipMenuEvent;
-import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
-import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
-import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.misc.DozeTrigger;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskViewHeader;
-import com.android.systemui.recents.views.TaskViewTransform;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An implementation of the Recents component for the current user.  For secondary users, this can
- * be called remotely from the system user.
- */
-public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
-
-    private final static String TAG = "RecentsImpl";
-
-    // The minimum amount of time between each recents button press that we will handle
-    private final static int MIN_TOGGLE_DELAY_MS = 350;
-
-    // The duration within which the user releasing the alt tab (from when they pressed alt tab)
-    // that the fast alt-tab animation will run.  If the user's alt-tab takes longer than this
-    // duration, then we will toggle recents after this duration.
-    private final static int FAST_ALT_TAB_DELAY_MS = 225;
-
-    private final static ArraySet<TaskKey> EMPTY_SET = new ArraySet<>();
-
-    public final static String RECENTS_PACKAGE = "com.android.systemui";
-    public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
-
-    /**
-     * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
-     * task stacks and update recents accordingly.
-     */
-    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
-
-        private OverviewProxyService mOverviewProxyService;
-
-        public TaskStackListenerImpl() {
-            mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        }
-
-        @Override
-        public void onTaskStackChangedBackground() {
-            // Skip background preloading recents in SystemUI if the overview services is bound
-            if (mOverviewProxyService.isEnabled()) {
-                return;
-            }
-
-            // Check this is for the right user
-            if (!checkCurrentUserId(mContext, false /* debug */)) {
-                return;
-            }
-
-            // Preloads the next task
-            RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-            if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) {
-                Rect windowRect = getWindowRect(null /* windowRectOverride */);
-                if (windowRect.isEmpty()) {
-                    return;
-                }
-
-                // Load the next task only if we aren't svelte
-                ActivityManager.RunningTaskInfo runningTaskInfo =
-                        ActivityManagerWrapper.getInstance().getRunningTask();
-                RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-                RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
-                loader.preloadTasks(plan, -1);
-                TaskStack stack = plan.getTaskStack();
-                RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
-                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-
-                synchronized (mBackgroundLayoutAlgorithm) {
-                    // This callback is made when a new activity is launched and the old one is
-                    // paused so ignore the current activity and try and preload the thumbnail for
-                    // the previous one.
-                    updateDummyStackViewLayout(mBackgroundLayoutAlgorithm, stack, windowRect);
-
-                    // Launched from app is always the worst case (in terms of how many
-                    // thumbnails/tasks visible)
-                    launchState.launchedFromApp = true;
-                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
-                            -1 /* lastScrollPPresent */);
-                    VisibilityReport visibilityReport =
-                            mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
-                                    stack.getTasks());
-
-                    launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
-                    launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
-                    launchOpts.numVisibleTaskThumbnails = visibilityReport.numVisibleThumbnails;
-                    launchOpts.onlyLoadForCache = true;
-                    launchOpts.onlyLoadPausedActivities = true;
-                    launchOpts.loadThumbnails = true;
-                }
-                loader.loadTasks(plan, launchOpts);
-            }
-        }
-
-        @Override
-        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
-            // Check this is for the right user
-            if (!checkCurrentUserId(mContext, false /* debug */)) {
-                return;
-            }
-
-            // This time needs to be fetched the same way the last active time is fetched in
-            // {@link TaskRecord#touchActiveTime}
-            LegacyRecentsImpl.getConfiguration().getLaunchState().launchedFromPipApp = true;
-            LegacyRecentsImpl.getConfiguration().getLaunchState().launchedWithNextPipApp = false;
-            EventBus.getDefault().send(new ActivityPinnedEvent(taskId));
-            consumeInstanceLoadPlan();
-            sLastPipTime = System.currentTimeMillis();
-        }
-
-        @Override
-        public void onActivityUnpinned() {
-            // Check this is for the right user
-            if (!checkCurrentUserId(mContext, false /* debug */)) {
-                return;
-            }
-
-            EventBus.getDefault().send(new ActivityUnpinnedEvent());
-            sLastPipTime = -1;
-        }
-
-        @Override
-        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
-            // Check this is for the right user
-            if (!checkCurrentUserId(mContext, false /* debug */)) {
-                return;
-            }
-
-            EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
-        }
-    }
-
-    protected static RecentsTaskLoadPlan sInstanceLoadPlan;
-    // Stores the last pinned task time
-    protected static long sLastPipTime = -1;
-    // Stores whether we are waiting for a transition to/from recents to start. During this time,
-    // we disallow the user from manually toggling recents until the transition has started.
-    private static boolean mWaitingForTransitionStart = false;
-    // Stores whether or not the user toggled while we were waiting for a transition to/from
-    // recents. In this case, we defer the toggle state until then and apply it immediately after.
-    private static boolean mToggleFollowingTransitionStart = true;
-
-    private Runnable mResetToggleFlagListener = new Runnable() {
-        @Override
-        public void run() {
-            setWaitingForTransitionStart(false);
-        }
-    };
-
-    private TrustManager mTrustManager;
-    protected Context mContext;
-    protected Handler mHandler;
-    TaskStackListenerImpl mTaskStackListener;
-    boolean mDraggingInRecents;
-    boolean mLaunchedWhileDocking;
-
-    // Task launching
-    Rect mTmpBounds = new Rect();
-    TaskViewTransform mTmpTransform = new TaskViewTransform();
-    int mTaskBarHeight;
-
-    // Header (for transition)
-    TaskViewHeader mHeaderBar;
-    final Object mHeaderBarLock = new Object();
-    private TaskStackView mDummyStackView;
-    private TaskStackLayoutAlgorithm mBackgroundLayoutAlgorithm;
-
-    // Variables to keep track of if we need to start recents after binding
-    protected boolean mTriggeredFromAltTab;
-    protected long mLastToggleTime;
-    DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
-        @Override
-        public void run() {
-            // When this fires, then the user has not released alt-tab for at least
-            // FAST_ALT_TAB_DELAY_MS milliseconds
-            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
-                    DividerView.INVALID_RECENTS_GROW_TARGET);
-        }
-    });
-
-    private OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
-            new OverviewProxyService.OverviewProxyListener() {
-        @Override
-        public void onConnectionChanged(boolean isConnected) {
-            if (!isConnected) {
-                // Clear everything when the connection to the overview service
-                LegacyRecentsImpl.getTaskLoader().onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
-            }
-        }
-    };
-
-    // Used to reset the dummy stack view
-    private final TaskStack mEmptyTaskStack = new TaskStack();
-
-    public RecentsImpl(Context context) {
-        mContext = context;
-        mHandler = new Handler();
-        mBackgroundLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
-
-        // Initialize the static foreground thread
-        ForegroundThread.get();
-
-        // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl();
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-
-        // Initialize the static configuration resources
-        mDummyStackView = new TaskStackView(mContext);
-        reloadResources();
-
-        mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
-    }
-
-    public void onBootCompleted() {
-        // Skip preloading tasks if we are already bound to the service
-        if (Dependency.get(OverviewProxyService.class).isEnabled()) {
-            return;
-        }
-
-        // When we start, preload the data associated with the previous recent tasks.
-        // We can use a new plan since the caches will be the same.
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
-        loader.preloadTasks(plan, -1);
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.numVisibleTasks = loader.getIconCacheSize();
-        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
-        launchOpts.onlyLoadForCache = true;
-        loader.loadTasks(plan, launchOpts);
-    }
-
-    public void onConfigurationChanged() {
-        reloadResources();
-        mDummyStackView.reloadOnConfigurationChange();
-        synchronized (mBackgroundLayoutAlgorithm) {
-            mBackgroundLayoutAlgorithm.reloadOnConfigurationChange(mContext);
-        }
-    }
-
-    /**
-     * This is only called from the system user's Recents.  Secondary users will instead proxy their
-     * visibility change events through to the system user via
-     * {@link LegacyRecentsImpl#onBusEvent(RecentsVisibilityChangedEvent)}.
-     */
-    public void onVisibilityChanged(Context context, boolean visible) {
-        LegacyRecentsImpl.getSystemServices().setRecentsVisibility(visible);
-    }
-
-    /**
-     * This is only called from the system user's Recents.  Secondary users will instead proxy their
-     * visibility change events through to the system user via
-     * {@link LegacyRecentsImpl#onBusEvent(ScreenPinningRequestEvent)}.
-     */
-    public void onStartScreenPinning(Context context, int taskId) {
-        final StatusBar statusBar = getStatusBar();
-        if (statusBar != null) {
-            statusBar.showScreenPinningRequest(taskId, false);
-        }
-    }
-
-    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
-            boolean animate, int growTarget) {
-        final SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        final MutableBoolean isHomeStackVisible = new MutableBoolean(true);
-        final boolean isRecentsVisible = LegacyRecentsImpl.getSystemServices().isRecentsActivityVisible(
-                isHomeStackVisible);
-        final boolean fromHome = isHomeStackVisible.value;
-        final boolean launchedWhileDockingTask =
-                LegacyRecentsImpl.getSystemServices().getSplitScreenPrimaryStack() != null;
-
-        mTriggeredFromAltTab = triggeredFromAltTab;
-        mDraggingInRecents = draggingInRecents;
-        mLaunchedWhileDocking = launchedWhileDockingTask;
-        if (mFastAltTabTrigger.isAsleep()) {
-            // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
-            mFastAltTabTrigger.stopDozing();
-        } else if (mFastAltTabTrigger.isDozing()) {
-            // Fast alt-tab duration has not elapsed.  If this is triggered by a different
-            // showRecents() call, then ignore that call for now.
-            // TODO: We can not handle quick tabs that happen between the initial showRecents() call
-            //       that started the activity and the activity starting up.  The severity of this
-            //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
-            if (!triggeredFromAltTab) {
-                return;
-            }
-            mFastAltTabTrigger.stopDozing();
-        } else if (triggeredFromAltTab) {
-            // The fast alt-tab detector is not yet running, so start the trigger and wait for the
-            // hideRecents() call, or for the fast alt-tab duration to elapse
-            mFastAltTabTrigger.startDozing();
-            return;
-        }
-
-        try {
-            // Check if the top task is in the home stack, and start the recents activity
-            final boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
-            if (forceVisible || !isRecentsVisible) {
-                ActivityManager.RunningTaskInfo runningTask =
-                        ActivityManagerWrapper.getInstance().getRunningTask();
-                startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
-                        isHomeStackVisible.value || fromHome, animate, growTarget);
-            }
-        } catch (ActivityNotFoundException e) {
-            Log.e(TAG, "Failed to launch RecentsActivity", e);
-        }
-    }
-
-    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
-            // The user has released alt-tab before the trigger has run, so just show the next
-            // task immediately
-            showNextTask();
-
-            // Cancel the fast alt-tab trigger
-            mFastAltTabTrigger.stopDozing();
-            return;
-        }
-
-        // Defer to the activity to handle hiding recents, if it handles it, then it must still
-        // be visible
-        EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
-                triggeredFromHomeKey));
-    }
-
-    public void toggleRecents(int growTarget) {
-        if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
-            return;
-        }
-
-        // Skip this toggle if we are already waiting to trigger recents via alt-tab
-        if (mFastAltTabTrigger.isDozing()) {
-            return;
-        }
-
-        if (mWaitingForTransitionStart) {
-            mToggleFollowingTransitionStart = true;
-            return;
-        }
-
-        mDraggingInRecents = false;
-        mLaunchedWhileDocking = false;
-        mTriggeredFromAltTab = false;
-
-        try {
-            MutableBoolean isHomeStackVisible = new MutableBoolean(true);
-            long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
-
-            SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-            if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
-                RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-                RecentsActivityLaunchState launchState = config.getLaunchState();
-                if (!launchState.launchedWithAltTab) {
-                    if (LegacyRecentsImpl.getConfiguration().isGridEnabled) {
-                        // Has the user tapped quickly?
-                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
-                        if (isQuickTap) {
-                            EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
-                        } else {
-                            EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
-                        }
-                    } else {
-                        // Launch the next focused task
-                        EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
-                    }
-                } else {
-                    // If the user has toggled it too quickly, then just eat up the event here (it's
-                    // better than showing a janky screenshot).
-                    // NOTE: Ideally, the screenshot mechanism would take the window transform into
-                    // account
-                    if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
-                        return;
-                    }
-
-                    EventBus.getDefault().post(new ToggleRecentsEvent());
-                    mLastToggleTime = SystemClock.elapsedRealtime();
-                }
-                return;
-            } else {
-                // If the user has toggled it too quickly, then just eat up the event here (it's
-                // better than showing a janky screenshot).
-                // NOTE: Ideally, the screenshot mechanism would take the window transform into
-                // account
-                if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
-                    return;
-                }
-
-                // Otherwise, start the recents activity
-                ActivityManager.RunningTaskInfo runningTask =
-                        ActivityManagerWrapper.getInstance().getRunningTask();
-                startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
-                        isHomeStackVisible.value, true /* animate */, growTarget);
-
-                // Only close the other system windows if we are actually showing recents
-                ActivityManagerWrapper.getInstance().closeSystemWindows(
-                        SYSTEM_DIALOG_REASON_RECENT_APPS);
-                mLastToggleTime = SystemClock.elapsedRealtime();
-            }
-        } catch (ActivityNotFoundException e) {
-            Log.e(TAG, "Failed to launch RecentsActivity", e);
-        }
-    }
-
-    public void preloadRecents() {
-        if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
-            return;
-        }
-
-        // Skip preloading recents when keyguard is showing
-        final StatusBar statusBar = getStatusBar();
-        if (statusBar != null && statusBar.isKeyguardShowing()) {
-            return;
-        }
-
-        // Preload only the raw task list into a new load plan (which will be consumed by the
-        // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
-        // don't block the touch feedback on the nav bar button which triggers this.
-        mHandler.post(() -> {
-            SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-            if (!ssp.isRecentsActivityVisible(null)) {
-                ActivityManager.RunningTaskInfo runningTask =
-                        ActivityManagerWrapper.getInstance().getRunningTask();
-                if (runningTask == null) {
-                    return;
-                }
-
-                RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-                sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
-                loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
-                TaskStack stack = sInstanceLoadPlan.getTaskStack();
-                if (stack.getTaskCount() > 0) {
-                    // Only preload the icon (but not the thumbnail since it may not have been taken
-                    // for the pausing activity)
-                    preloadIcon(runningTask.id);
-
-                    // At this point, we don't know anything about the stack state.  So only
-                    // calculate the dimensions of the thumbnail that we need for the transition
-                    // into Recents, but do not draw it until we construct the activity options when
-                    // we start Recents
-                    updateHeaderBarLayout(stack, null /* window rect override*/);
-                }
-            }
-        });
-    }
-
-    public void cancelPreloadingRecents() {
-        // Do nothing
-    }
-
-    public void onDraggingInRecents(float distanceFromTop) {
-        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
-    }
-
-    public void onDraggingInRecentsEnded(float velocity) {
-        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
-    }
-
-    public void onShowCurrentUserToast(int msgResId, int msgLength) {
-        Toast.makeText(mContext, msgResId, msgLength).show();
-    }
-
-    /**
-     * Transitions to the next recent task in the stack.
-     */
-    public void showNextTask() {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
-        loader.preloadTasks(plan, -1);
-        TaskStack focusedStack = plan.getTaskStack();
-
-        // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
-
-        // Return early if there is no running task
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        if (runningTask == null) return;
-
-        // Find the task in the recents list
-        boolean isRunningTaskInHomeStack =
-                runningTask.configuration.windowConfiguration.getActivityType()
-                        == ACTIVITY_TYPE_HOME;
-        ArrayList<Task> tasks = focusedStack.getTasks();
-        Task toTask = null;
-        ActivityOptions launchOpts = null;
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 1; i--) {
-            Task task = tasks.get(i);
-            if (isRunningTaskInHomeStack) {
-                toTask = tasks.get(i - 1);
-                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                        R.anim.recents_launch_next_affiliated_task_target,
-                        R.anim.recents_fast_toggle_app_home_exit);
-                break;
-            } else if (task.key.id == runningTask.id) {
-                toTask = tasks.get(i - 1);
-                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                        R.anim.recents_launch_prev_affiliated_task_target,
-                        R.anim.recents_launch_prev_affiliated_task_source);
-                break;
-            }
-        }
-
-        // Return early if there is no next task
-        if (toTask == null) {
-            ssp.startInPlaceAnimationOnFrontMostApplication(
-                    ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                            R.anim.recents_launch_prev_affiliated_task_bounce));
-            return;
-        }
-
-        // Launch the task
-        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
-                null /* resultCallback */, null /* resultCallbackHandler */);
-    }
-
-    /**
-     * Transitions to the next affiliated task.
-     */
-    public void showRelativeAffiliatedTask(boolean showNextTask) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
-        loader.preloadTasks(plan, -1);
-        TaskStack focusedStack = plan.getTaskStack();
-
-        // Return early if there are no tasks in the focused stack
-        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
-
-        // Return early if there is no running task (can't determine affiliated tasks in this case)
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
-        if (runningTask == null) return;
-        // Return early if the running task is in the home/recents stack (optimization)
-        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
-
-        // Find the task in the recents list
-        ArrayList<Task> tasks = focusedStack.getTasks();
-        Task toTask = null;
-        ActivityOptions launchOpts = null;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == runningTask.id) {
-                if (showNextTask) {
-                    if ((i + 1) < taskCount) {
-                        toTask = tasks.get(i + 1);
-                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                                R.anim.recents_launch_next_affiliated_task_target,
-                                R.anim.recents_launch_next_affiliated_task_source);
-                    }
-                } else {
-                    if ((i - 1) >= 0) {
-                        toTask = tasks.get(i - 1);
-                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                                R.anim.recents_launch_prev_affiliated_task_target,
-                                R.anim.recents_launch_prev_affiliated_task_source);
-                    }
-                }
-                break;
-            }
-        }
-
-        // Return early if there is no next task
-        if (toTask == null) {
-            if (showNextTask) {
-                ssp.startInPlaceAnimationOnFrontMostApplication(
-                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                R.anim.recents_launch_next_affiliated_task_bounce));
-            } else {
-                ssp.startInPlaceAnimationOnFrontMostApplication(
-                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                R.anim.recents_launch_prev_affiliated_task_bounce));
-            }
-            return;
-        }
-
-        // Keep track of actually launched affiliated tasks
-        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
-
-        // Launch the task
-        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
-                null /* resultListener */, null /* resultCallbackHandler */);
-    }
-
-    public void splitPrimaryTask(int taskId, int stackCreateMode, Rect initialBounds) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-
-        // Make sure we inform DividerView before we actually start the activity so we can change
-        // the resize mode already.
-        if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
-            EventBus.getDefault().send(new DockedTopTaskEvent(initialBounds));
-        }
-    }
-
-    public void setWaitingForTransitionStart(boolean waitingForTransitionStart) {
-        if (mWaitingForTransitionStart == waitingForTransitionStart) {
-            return;
-        }
-
-        mWaitingForTransitionStart = waitingForTransitionStart;
-        if (!waitingForTransitionStart && mToggleFollowingTransitionStart) {
-            mHandler.post(() -> toggleRecents(DividerView.INVALID_RECENTS_GROW_TARGET));
-        }
-        mToggleFollowingTransitionStart = false;
-    }
-
-    /**
-     * Returns the preloaded load plan and invalidates it.
-     */
-    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
-        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
-        sInstanceLoadPlan = null;
-        return plan;
-    }
-
-    /**
-     * @return the time at which a task last entered picture-in-picture.
-     */
-    public static long getLastPipTime() {
-        return sLastPipTime;
-    }
-
-    /**
-     * Clears the time at which a task last entered picture-in-picture.
-     */
-    public static void clearLastPipTime() {
-        sLastPipTime = -1;
-    }
-
-    /**
-     * Reloads all the resources for the current configuration.
-     */
-    private void reloadResources() {
-        Resources res = mContext.getResources();
-
-        mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_grid_task_view_header_height);
-
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
-                null, false);
-        mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
-    }
-
-    private void updateDummyStackViewLayout(TaskStackLayoutAlgorithm stackLayout,
-            TaskStack stack, Rect windowRect) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        Rect displayRect = ssp.getDisplayRect();
-        Rect systemInsets = new Rect();
-        ssp.getStableInsets(systemInsets);
-
-        // When docked, the nav bar insets are consumed and the activity is measured without insets.
-        // However, the window bounds include the insets, so we need to subtract them here to make
-        // them identical.
-        if (ssp.hasDockedTask()) {
-            if (systemInsets.bottom < windowRect.height()) {
-                // Only apply inset if it isn't going to cause the rect height to go negative.
-                windowRect.bottom -= systemInsets.bottom;
-            }
-            systemInsets.bottom = 0;
-        }
-        calculateWindowStableInsets(systemInsets, windowRect, displayRect);
-        windowRect.offsetTo(0, 0);
-
-        // Rebind the header bar and draw it for the transition
-        stackLayout.setSystemInsets(systemInsets);
-        if (stack != null) {
-            stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
-                    systemInsets.left, systemInsets.right, mTmpBounds);
-            stackLayout.reset();
-            stackLayout.initialize(displayRect, windowRect, mTmpBounds);
-        }
-    }
-
-    private Rect getWindowRect(Rect windowRectOverride) {
-       return windowRectOverride != null
-                ? new Rect(windowRectOverride)
-                : LegacyRecentsImpl.getSystemServices().getWindowRect();
-    }
-
-    /**
-     * Prepares the header bar layout for the next transition, if the task view bounds has changed
-     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
-     *
-     * @param stack the stack to initialize the stack layout with
-     * @param windowRectOverride the rectangle to use when calculating the stack state which can
-     *                           be different from the current window rect if recents is resizing
-     *                           while being launched
-     */
-    private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
-        Rect windowRect = getWindowRect(windowRectOverride);
-        int taskViewWidth = 0;
-        boolean useGridLayout = mDummyStackView.useGridLayout();
-        updateDummyStackViewLayout(mDummyStackView.getStackAlgorithm(), stack, windowRect);
-        if (stack != null) {
-            TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
-            mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
-            mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
-            // Get the width of a task view so that we know how wide to draw the header bar.
-            if (useGridLayout) {
-                TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
-                gridLayout.initialize(windowRect);
-                taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
-                        stack.getTaskCount(), new TaskViewTransform(),
-                        stackLayout).rect.width();
-            } else {
-                Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
-                if (!taskViewBounds.isEmpty()) {
-                    taskViewWidth = taskViewBounds.width();
-                }
-            }
-        }
-
-        if (stack != null && taskViewWidth > 0) {
-            synchronized (mHeaderBarLock) {
-                if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
-                        mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
-                    if (useGridLayout) {
-                        mHeaderBar.setShouldDarkenBackgroundColor(true);
-                        mHeaderBar.setNoUserInteractionState();
-                    }
-                    mHeaderBar.forceLayout();
-                    mHeaderBar.measure(
-                            MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
-                            MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
-                }
-                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
-            }
-        }
-    }
-
-    /**
-     * Given the stable insets and the rect for our window, calculates the insets that affect our
-     * window.
-     */
-    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect, Rect displayRect) {
-
-        // Display rect without insets - available app space
-        Rect appRect = new Rect(displayRect);
-        appRect.inset(inOutInsets);
-
-        // Our window intersected with available app space
-        Rect windowRectWithInsets = new Rect(windowRect);
-        windowRectWithInsets.intersect(appRect);
-        inOutInsets.left = windowRectWithInsets.left - windowRect.left;
-        inOutInsets.top = windowRectWithInsets.top - windowRect.top;
-        inOutInsets.right = windowRect.right - windowRectWithInsets.right;
-        inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
-    }
-
-    /**
-     * Preloads the icon of a task.
-     */
-    private void preloadIcon(int runningTaskId) {
-        // Ensure that we load the running task's icon
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.runningTaskId = runningTaskId;
-        launchOpts.loadThumbnails = false;
-        launchOpts.onlyLoadForCache = true;
-        LegacyRecentsImpl.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts);
-    }
-
-    /**
-     * Creates the activity options for a unknown state->recents transition.
-     */
-    protected ActivityOptions getUnknownTransitionActivityOptions() {
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_unknown_enter,
-                R.anim.recents_from_unknown_exit,
-                mHandler, null);
-    }
-
-    /**
-     * Creates the activity options for a home->recents transition.
-     */
-    protected ActivityOptions getHomeTransitionActivityOptions() {
-        return ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.recents_from_launcher_enter,
-                R.anim.recents_from_launcher_exit,
-                mHandler, null);
-    }
-
-    /**
-     * Creates the activity options for an app->recents transition.
-     */
-    private Pair<ActivityOptions, AppTransitionAnimationSpecsFuture>
-            getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
-                    Rect windowOverrideRect) {
-        final boolean isLowRamDevice = LegacyRecentsImpl.getConfiguration().isLowRamDevice;
-
-        // Update the destination rect
-        Task toTask = new Task();
-        TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
-                windowOverrideRect);
-
-        RectF toTaskRect = toTransform.rect;
-        AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
-            @Override
-            public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                Rect rect = new Rect();
-                toTaskRect.round(rect);
-                Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
-                return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id,
-                        thumbnail, rect));
-            }
-        };
-
-        // For low end ram devices, wait for transition flag is reset when Recents entrance
-        // animation is complete instead of when the transition animation starts
-        return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
-                false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
-                future);
-    }
-
-    /**
-     * Returns the transition rect for the given task id.
-     */
-    private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
-            Task runningTaskOut, Rect windowOverrideRect) {
-        // Find the running task in the TaskStack
-        TaskStack stack = stackView.getStack();
-        Task launchTask = stack.getLaunchTarget();
-        if (launchTask != null) {
-            runningTaskOut.copyFrom(launchTask);
-        } else {
-            // If no task is specified or we can not find the task just use the front most one
-            launchTask = stack.getFrontMostTask();
-            runningTaskOut.copyFrom(launchTask);
-        }
-
-        // Get the transform for the running task
-        stackView.updateLayoutAlgorithm(true /* boundScroll */);
-        stackView.updateToInitialState();
-        stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
-                stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
-        return mTmpTransform;
-    }
-
-    /**
-     * Draws the header of a task used for the window animation into a bitmap.
-     */
-    private Bitmap drawThumbnailTransitionBitmap(Task toTask,
-            TaskViewTransform toTransform) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        int width = (int) toTransform.rect.width();
-        int height = (int) toTransform.rect.height();
-        if (toTransform != null && toTask.key != null && width > 0 && height > 0) {
-            synchronized (mHeaderBarLock) {
-                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
-                mHeaderBar.onTaskViewSizeChanged(width, height);
-                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                    return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
-                            null, 1f, 0xFFff0000);
-                } else {
-                    // Workaround for b/27815919, reset the callback so that we do not trigger an
-                    // invalidate on the header bar as a result of updating the icon
-                    Drawable icon = mHeaderBar.getIconView().getDrawable();
-                    if (icon != null) {
-                        icon.setCallback(null);
-                    }
-                    mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
-                            disabledInSafeMode);
-                    mHeaderBar.onTaskDataLoaded();
-                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
-                    return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
-                            mHeaderBar, 1f, 0);
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Shows the recents activity after dismissing the keyguard if visible
-     */
-    protected void startRecentsActivityAndDismissKeyguardIfNeeded(
-            final ActivityManager.RunningTaskInfo runningTask, final boolean isHomeStackVisible,
-            final boolean animate, final int growTarget) {
-        // Preload only if device for current user is unlocked
-        final StatusBar statusBar = getStatusBar();
-        if (statusBar != null && statusBar.isKeyguardShowing()) {
-            statusBar.executeRunnableDismissingKeyguard(() -> {
-                    // Flush trustmanager before checking device locked per user when preloading
-                    mTrustManager.reportKeyguardShowingChanged();
-                    mHandler.post(() -> startRecentsActivity(runningTask, isHomeStackVisible,
-                            animate, growTarget));
-                }, null,  true /* dismissShade */, false /* afterKeyguardGone */,
-                true /* deferred */);
-        } else {
-            startRecentsActivity(runningTask, isHomeStackVisible, animate, growTarget);
-        }
-    }
-
-    private void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
-            boolean isHomeStackVisible, boolean animate, int growTarget) {
-        RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader();
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-
-        int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
-                ? runningTask.id
-                : -1;
-
-        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
-        // should always preload the tasks now. If we are dragging in recents, reload them as
-        // the stacks might have changed.
-        if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
-            // Create a new load plan if preloadRecents() was never triggered
-            sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
-        }
-        if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, runningTaskId);
-        }
-
-        TaskStack stack = sInstanceLoadPlan.getTaskStack();
-        boolean hasRecentTasks = stack.getTaskCount() > 0;
-        boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible &&
-                hasRecentTasks;
-
-        // Update the launch state that we need in updateHeaderBarLayout()
-        launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
-        launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
-        launchState.launchedFromPipApp = false;
-        launchState.launchedWithNextPipApp =
-                stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
-        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
-        launchState.launchedViaDragGesture = mDraggingInRecents;
-        launchState.launchedToTaskId = runningTaskId;
-        launchState.launchedWithAltTab = mTriggeredFromAltTab;
-
-        // Disable toggling of recents between starting the activity and it is visible and the app
-        // has started its transition into recents.
-        setWaitingForTransitionStart(useThumbnailTransition);
-
-        // Preload the icon (this will be a null-op if we have preloaded the icon already in
-        // preloadRecents())
-        preloadIcon(runningTaskId);
-
-        // Update the header bar if necessary
-        Rect windowOverrideRect = getWindowRectOverride(growTarget);
-        updateHeaderBarLayout(stack, windowOverrideRect);
-
-        // Prepare the dummy stack for the transition
-        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
-                mDummyStackView.computeStackVisibilityReport();
-
-        // Update the remaining launch state
-        launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
-        launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
-
-        if (!animate) {
-            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1),
-                    null /* future */);
-            return;
-        }
-
-        Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
-        if (useThumbnailTransition) {
-            // Try starting with a thumbnail transition
-            pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
-        } else {
-            // If there is no thumbnail transition, but is launching from home into recents, then
-            // use a quick home transition
-            pair = new Pair<>(hasRecentTasks
-                    ? getHomeTransitionActivityOptions()
-                    : getUnknownTransitionActivityOptions(), null);
-        }
-        startRecentsActivity(pair.first, pair.second);
-        mLastToggleTime = SystemClock.elapsedRealtime();
-    }
-
-    private Rect getWindowRectOverride(int growTarget) {
-        if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
-            return SystemServicesProxy.getInstance(mContext).getWindowRect();
-        }
-        Rect result = new Rect();
-        Rect displayRect = LegacyRecentsImpl.getSystemServices().getDisplayRect();
-        DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
-                result, displayRect.width(), displayRect.height(),
-                LegacyRecentsImpl.getSystemServices().getDockedDividerSize(mContext));
-        return result;
-    }
-
-    private StatusBar getStatusBar() {
-        return SysUiServiceProvider.getComponent(mContext, StatusBar.class);
-    }
-
-    /**
-     * Starts the recents activity.
-     */
-    private void startRecentsActivity(ActivityOptions opts,
-            final AppTransitionAnimationSpecsFuture future) {
-        Intent intent = new Intent();
-        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-        HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
-        hideMenuEvent.addPostAnimationCallback(() -> {
-            LegacyRecentsImpl.getSystemServices().startActivityAsUserAsync(intent, opts);
-            EventBus.getDefault().send(new RecentsActivityStartingEvent());
-            if (future != null) {
-                future.composeSpecsSynchronous();
-            }
-        });
-        EventBus.getDefault().send(hideMenuEvent);
-
-        // Once we have launched the activity, reset the dummy stack view tasks so we don't hold
-        // onto references to the same tasks consumed by the activity
-        mDummyStackView.setTasks(mEmptyTaskStack, false /* notifyStackChanges */);
-    }
-
-    /**** OnAnimationFinishedListener Implementation ****/
-
-    @Override
-    public void onAnimationFinished() {
-        EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImplProxy.java
deleted file mode 100644
index a1da785..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsImplProxy.java
+++ /dev/null
@@ -1,161 +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.recents;
-
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-
-import com.android.internal.os.SomeArgs;
-
-/**
- * A proxy class which directs all methods from {@link IRecentsNonSystemUserCallbacks} to
- * {@link RecentsImpl} and makes sure they are called from the main thread.
- */
-public class RecentsImplProxy extends IRecentsNonSystemUserCallbacks.Stub {
-
-    private static final int MSG_PRELOAD_RECENTS = 1;
-    private static final int MSG_CANCEL_PRELOADING_RECENTS = 2;
-    private static final int MSG_SHOW_RECENTS = 3;
-    private static final int MSG_HIDE_RECENTS = 4;
-    private static final int MSG_TOGGLE_RECENTS = 5;
-    private static final int MSG_ON_CONFIGURATION_CHANGED = 6;
-    private static final int MSG_DOCK_TOP_TASK = 7;
-    private static final int MSG_ON_DRAGGING_IN_RECENTS = 8;
-    private static final int MSG_ON_DRAGGING_IN_RECENTS_ENDED = 9;
-    private static final int MSG_SHOW_USER_TOAST = 10;
-
-    private RecentsImpl mImpl;
-
-    public RecentsImplProxy(RecentsImpl recentsImpl) {
-        mImpl = recentsImpl;
-    }
-
-    @Override
-    public void preloadRecents() throws RemoteException {
-        mHandler.sendEmptyMessage(MSG_PRELOAD_RECENTS);
-    }
-
-    @Override
-    public void cancelPreloadingRecents() throws RemoteException {
-        mHandler.sendEmptyMessage(MSG_CANCEL_PRELOADING_RECENTS);
-    }
-
-    @Override
-    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents, boolean animate,
-            int growTarget) throws RemoteException {
-        SomeArgs args = SomeArgs.obtain();
-        args.argi1 = triggeredFromAltTab ? 1 : 0;
-        args.argi2 = draggingInRecents ? 1 : 0;
-        args.argi3 = animate ? 1 : 0;
-        args.argi4 = growTarget;
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_SHOW_RECENTS, args));
-    }
-
-    @Override
-    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey)
-            throws RemoteException {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_HIDE_RECENTS, triggeredFromAltTab ? 1 :0,
-                triggeredFromHomeKey ? 1 : 0));
-    }
-
-    @Override
-    public void toggleRecents(int growTarget) throws RemoteException {
-        SomeArgs args = SomeArgs.obtain();
-        args.argi1 = growTarget;
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_TOGGLE_RECENTS, args));
-    }
-
-    @Override
-    public void onConfigurationChanged() throws RemoteException {
-        mHandler.sendEmptyMessage(MSG_ON_CONFIGURATION_CHANGED);
-    }
-
-    @Override
-    public void splitPrimaryTask(int topTaskId, int stackCreateMode, Rect initialBounds)
-            throws RemoteException {
-        SomeArgs args = SomeArgs.obtain();
-        args.argi1 = topTaskId;
-        args.argi2 = stackCreateMode;
-        args.arg1 = initialBounds;
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_DOCK_TOP_TASK, args));
-    }
-
-    @Override
-    public void onDraggingInRecents(float distanceFromTop) throws RemoteException {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DRAGGING_IN_RECENTS, distanceFromTop));
-    }
-
-    @Override
-    public void onDraggingInRecentsEnded(float velocity) throws RemoteException {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DRAGGING_IN_RECENTS_ENDED, velocity));
-    }
-
-    @Override
-    public void showCurrentUserToast(int msgResId, int msgLength) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_SHOW_USER_TOAST, msgResId, msgLength));
-    }
-
-    private final Handler mHandler = new Handler() {
-
-        @Override
-        public void handleMessage(Message msg) {
-            SomeArgs args;
-            switch (msg.what) {
-                case MSG_PRELOAD_RECENTS:
-                    mImpl.preloadRecents();
-                    break;
-                case MSG_CANCEL_PRELOADING_RECENTS:
-                    mImpl.cancelPreloadingRecents();
-                    break;
-                case MSG_SHOW_RECENTS:
-                    args = (SomeArgs) msg.obj;
-                    mImpl.showRecents(args.argi1 != 0, args.argi2 != 0, args.argi3 != 0,
-                            args.argi4);
-                    break;
-                case MSG_HIDE_RECENTS:
-                    mImpl.hideRecents(msg.arg1 != 0, msg.arg2 != 0);
-                    break;
-                case MSG_TOGGLE_RECENTS:
-                    args = (SomeArgs) msg.obj;
-                    mImpl.toggleRecents(args.argi1);
-                    break;
-                case MSG_ON_CONFIGURATION_CHANGED:
-                    mImpl.onConfigurationChanged();
-                    break;
-                case MSG_DOCK_TOP_TASK:
-                    args = (SomeArgs) msg.obj;
-                    mImpl.splitPrimaryTask(args.argi1, args.argi2 = 0,
-                            (Rect) args.arg1);
-                    break;
-                case MSG_ON_DRAGGING_IN_RECENTS:
-                    mImpl.onDraggingInRecents((Float) msg.obj);
-                    break;
-                case MSG_ON_DRAGGING_IN_RECENTS_ENDED:
-                    mImpl.onDraggingInRecentsEnded((Float) msg.obj);
-                    break;
-                case MSG_SHOW_USER_TOAST:
-                    mImpl.onShowCurrentUserToast(msg.arg1, msg.arg2);
-                    break;
-                default:
-                    super.handleMessage(msg);
-            }
-            super.handleMessage(msg);
-        }
-    };
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUser.java
deleted file mode 100644
index c5e9f04..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUser.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.EventLogConstants;
-import com.android.systemui.EventLogTags;
-import com.android.systemui.pip.phone.ForegroundThread;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
-import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
-
-/**
- * An implementation of the system user's Recents interface to be called remotely by secondary
- * users.
- */
-public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
-
-    private static final String TAG = "RecentsSystemUser";
-
-    private Context mContext;
-    private RecentsImpl mImpl;
-    private final SparseArray<IRecentsNonSystemUserCallbacks> mNonSystemUserRecents =
-            new SparseArray<>();
-
-    public RecentsSystemUser(Context context, RecentsImpl impl) {
-        mContext = context;
-        mImpl = impl;
-    }
-
-    @Override
-    public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks,
-            final int userId) {
-        try {
-            final IRecentsNonSystemUserCallbacks callback =
-                    IRecentsNonSystemUserCallbacks.Stub.asInterface(nonSystemUserCallbacks);
-            nonSystemUserCallbacks.linkToDeath(new IBinder.DeathRecipient() {
-                @Override
-                public void binderDied() {
-                    mNonSystemUserRecents.removeAt(mNonSystemUserRecents.indexOfValue(callback));
-                    EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
-                            EventLogConstants.SYSUI_RECENTS_CONNECTION_SYSTEM_UNREGISTER_USER,
-                            userId);
-                }
-            }, 0);
-            mNonSystemUserRecents.put(userId, callback);
-            EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_CONNECTION,
-                    EventLogConstants.SYSUI_RECENTS_CONNECTION_SYSTEM_REGISTER_USER, userId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to register NonSystemUserCallbacks", e);
-        }
-    }
-
-    public IRecentsNonSystemUserCallbacks getNonSystemUserRecentsForUser(int userId) {
-        return mNonSystemUserRecents.get(userId);
-    }
-
-    @Override
-    public void updateRecentsVisibility(boolean visible) {
-        ForegroundThread.getHandler().post(() -> {
-            mImpl.onVisibilityChanged(mContext, visible);
-        });
-    }
-
-    @Override
-    public void startScreenPinning(int taskId) {
-        ForegroundThread.getHandler().post(() -> {
-            mImpl.onStartScreenPinning(mContext, taskId);
-        });
-    }
-
-    @Override
-    public void sendRecentsDrawnEvent() {
-        EventBus.getDefault().post(new RecentsDrawnEvent());
-    }
-
-    @Override
-    public void sendDockingTopTaskEvent(Rect initialRect) throws RemoteException {
-        EventBus.getDefault().post(new DockedTopTaskEvent(initialRect));
-    }
-
-    @Override
-    public void sendLaunchRecentsEvent() throws RemoteException {
-        EventBus.getDefault().post(new RecentsActivityStartingEvent());
-    }
-
-    @Override
-    public void sendDockedFirstAnimationFrameEvent() throws RemoteException {
-        EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
-    }
-
-    @Override
-    public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
-        EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
-                waitingForTransitionStart));
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUserService.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUserService.java
deleted file mode 100644
index b5a0181..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/RecentsSystemUserService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.systemui.SysUiServiceProvider;
-
-/**
- * A strictly system-user service that is started by the secondary user's Recents (with a limited
- * lifespan), to get the interface that the secondary user's Recents can call through to the system
- * user's Recents.
- */
-public class RecentsSystemUserService extends Service {
-
-    private static final String TAG = "RecentsSystemUserService";
-    private static final boolean DEBUG = false;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        LegacyRecentsImpl recents = SysUiServiceProvider.getComponent(this, LegacyRecentsImpl.class);
-        if (DEBUG) {
-            Log.d(TAG, "onBind: " + recents);
-        }
-        if (recents != null) {
-            return recents.getSystemUserCallbacks();
-        }
-        return null;
-    }
-}
-
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/EventBus.java
deleted file mode 100644
index 177362c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/EventBus.java
+++ /dev/null
@@ -1,763 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Represents a subscriber, which implements various event bus handler methods.
- */
-class Subscriber {
-    private WeakReference<Object> mSubscriber;
-
-    long registrationTime;
-
-    Subscriber(Object subscriber, long registrationTime) {
-        mSubscriber = new WeakReference<>(subscriber);
-        this.registrationTime = registrationTime;
-    }
-
-    public String toString(int priority) {
-        Object sub = mSubscriber.get();
-        String id = Integer.toHexString(System.identityHashCode(sub));
-        return sub.getClass().getSimpleName() + " [0x" + id + ", P" + priority + "]";
-    }
-
-    public Object getReference() {
-        return mSubscriber.get();
-    }
-}
-
-/**
- * Represents an event handler with a priority.
- */
-class EventHandler {
-    int priority;
-    Subscriber subscriber;
-    EventHandlerMethod method;
-
-    EventHandler(Subscriber subscriber, EventHandlerMethod method, int priority) {
-        this.subscriber = subscriber;
-        this.method = method;
-        this.priority = priority;
-    }
-
-    @Override
-    public String toString() {
-        return subscriber.toString(priority) + " " + method.toString();
-    }
-}
-
-/**
- * Represents the low level method handling a particular event.
- */
-class EventHandlerMethod {
-    private Method mMethod;
-    Class<? extends EventBus.Event> eventType;
-
-    EventHandlerMethod(Method method, Class<? extends EventBus.Event> eventType) {
-        mMethod = method;
-        mMethod.setAccessible(true);
-        this.eventType = eventType;
-    }
-
-    public void invoke(Object target, EventBus.Event event)
-            throws InvocationTargetException, IllegalAccessException {
-        mMethod.invoke(target, event);
-    }
-
-    @Override
-    public String toString() {
-        return mMethod.getName() + "(" + eventType.getSimpleName() + ")";
-    }
-}
-
-/**
- * A simple in-process event bus.  It is simple because we can make assumptions about the state of
- * SystemUI and Recent's lifecycle.
- *
- * <p>
- * Currently, there is a single EventBus that handles {@link EventBus.Event}s for each subscriber
- * on the main application thread.  Publishers can send() events to synchronously call subscribers
- * of that event, or post() events to be processed in the next run of the {@link Looper}.
- *
- * <p>
- * Subscribers must be registered with a particular EventBus before they will receive events, and
- * handler methods must match a specific signature.
- *
- * <p>
- * Event method signature:<ul>
- * <li>Methods must be public final
- * <li>Methods must return void
- * <li>Methods must be called "onBusEvent"
- * <li>Methods must take one parameter, of class type deriving from {@link EventBus.Event}
- * </ul>
- *
- * </p>
- * Each subscriber can be registered with a given priority (default 1), and events will be dispatch
- * in decreasing order of priority.  For subscribers with the same priority, events will be
- * dispatched by latest registration time to earliest.
- *
- * <p>
- * Caveats:<ul>
- * <li>The EventBus keeps a {@link WeakReference} to the publisher to prevent memory leaks, so
- * there must be another strong reference to the publisher for it to not get garbage-collected and
- * continue receiving events.
- * <li>Because the event handlers are called back using reflection, the EventBus is not intended
- * for use in tight, performance criticial loops.  For most user input/system callback events, this
- * is generally of low enough frequency to use the EventBus.
- * <li>Because the event handlers are called back using reflection, there will often be no
- * references to them from actual code.  The proguard configuration will be need to be updated to
- * keep these extra methods:
- *
- * -keepclassmembers class ** {
- * public void onBusEvent(**);
- * public void onInterprocessBusEvent(**);
- * }
- * -keepclassmembers class ** extends **.EventBus$InterprocessEvent {
- * public <init>(android.os.Bundle);
- * }
- *
- * <li>Subscriber registration can be expensive depending on the subscriber's {@link Class}.  This
- * is only done once per class type, but if possible, it is best to pre-register an instance of
- * that class beforehand or when idle.
- * <li>Each event should be sent once.  Events may hold internal information about the current
- * dispatch, or may be queued to be dispatched on another thread (if posted from a non-main thread),
- * so it may be unsafe to edit, change, or re-send the event again.
- * <li>Events should follow a pattern of public-final POD (plain old data) objects, where they are
- * initialized by the constructor and read by each subscriber of that event.  Subscribers should
- * never alter events as they are processed, and this enforces that pattern.
- * </ul>
- *
- * <p>
- * Future optimizations:
- * <li>throw exception/log when a subscriber loses the reference
- * <li>trace cost per registration & invocation
- * <li>trace cross-process invocation
- * <li>register(subscriber, Class&lt;?&gt;...) -- pass in exact class types you want registered
- * <li>setSubscriberEventHandlerPriority(subscriber, Class<Event>, priority)
- * <li>allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a
- * message before invocation (ie. check if task id == this task id)
- * <li>add postOnce() which automatically debounces
- * <li>add postDelayed() which delays / postDelayedOnce() which delays and bounces
- * <li>consolidate register() and registerInterprocess()
- * <li>sendForResult&lt;ReturnType&gt;(Event) to send and get a result, but who will send the
- * result?
- * </p>
- */
-public class EventBus {
-
-    private static final String TAG = "EventBus";
-    private static final boolean DEBUG_TRACE_ALL = false;
-
-    /**
-     * An event super class that allows us to track internal event state across subscriber
-     * invocations.
-     *
-     * Events should not be edited by subscribers.
-     */
-    public static class Event implements Cloneable {
-        // Indicates that this event's dispatch should be traced and logged to logcat
-        boolean trace;
-        // Indicates that this event must be posted on the EventBus's looper thread before invocation
-        boolean requiresPost;
-        // Not currently exposed, allows a subscriber to cancel further dispatch of this event
-        boolean cancelled;
-
-        // Only accessible from derived events
-        protected Event() {}
-
-        /**
-         * Called by the EventBus prior to dispatching this event to any subscriber of this event.
-         */
-        void onPreDispatch() {
-            // Do nothing
-        }
-
-        /**
-         * Called by the EventBus after dispatching this event to every subscriber of this event.
-         */
-        void onPostDispatch() {
-            // Do nothing
-        }
-
-        @Override
-        protected Object clone() throws CloneNotSupportedException {
-            Event evt = (Event) super.clone();
-            // When cloning an event, reset the cancelled-dispatch state
-            evt.cancelled = false;
-            return evt;
-        }
-    }
-
-    /**
-     * An event that represents an animated state change, which allows subscribers to coordinate
-     * callbacks which happen after the animation has taken place.
-     *
-     * Internally, it is guaranteed that increment() and decrement() will be called before and the
-     * after the event is dispatched.
-     */
-    public static class AnimatedEvent extends Event {
-
-        private final ReferenceCountedTrigger mTrigger = new ReferenceCountedTrigger();
-
-        // Only accessible from derived events
-        protected AnimatedEvent() {}
-
-        /**
-         * Returns the reference counted trigger that coordinates the animations for this event.
-         */
-        public ReferenceCountedTrigger getAnimationTrigger() {
-            return mTrigger;
-        }
-
-        /**
-         * Adds a callback that is guaranteed to be called after the state has changed regardless of
-         * whether an actual animation took place.
-         */
-        public void addPostAnimationCallback(Runnable r) {
-            mTrigger.addLastDecrementRunnable(r);
-        }
-
-        @Override
-        void onPreDispatch() {
-            mTrigger.increment();
-        }
-
-        @Override
-        void onPostDispatch() {
-            mTrigger.decrement();
-        }
-
-        @Override
-        protected Object clone() throws CloneNotSupportedException {
-            throw new CloneNotSupportedException();
-        }
-    }
-
-    /**
-     * An event that can be reusable, only used for situations where we want to reduce memory
-     * allocations when events are sent frequently (ie. on scroll).
-     */
-    public static class ReusableEvent extends Event {
-
-        private int mDispatchCount;
-
-        protected ReusableEvent() {}
-
-        @Override
-        void onPostDispatch() {
-            super.onPostDispatch();
-            mDispatchCount++;
-        }
-
-        @Override
-        protected Object clone() throws CloneNotSupportedException {
-            throw new CloneNotSupportedException();
-        }
-    }
-
-    /**
-     * Proguard must also know, and keep, all methods matching this signature.
-     *
-     * -keepclassmembers class ** {
-     *     public void onBusEvent(**);
-     *     public void onInterprocessBusEvent(**);
-     * }
-     */
-    private static final String METHOD_PREFIX = "onBusEvent";
-
-    // The default priority of all subscribers
-    private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
-
-    // Orders the handlers by priority and registration time
-    private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
-        @Override
-        public int compare(EventHandler h1, EventHandler h2) {
-            // Rank the handlers by priority descending, followed by registration time descending.
-            // aka. the later registered
-            if (h1.priority != h2.priority) {
-                return h2.priority - h1.priority;
-            } else {
-                return Long.compare(h2.subscriber.registrationTime, h1.subscriber.registrationTime);
-            }
-        }
-    };
-
-    // Used for initializing the default bus
-    private static final Object sLock = new Object();
-    private static volatile EventBus sDefaultBus;
-
-    // The handler to post all events
-    private Handler mHandler;
-
-    /**
-     * Map from event class -> event handler list.  Keeps track of the actual mapping from event
-     * to subscriber method.
-     */
-    private HashMap<Class<? extends Event>, ArrayList<EventHandler>> mEventTypeMap = new HashMap<>();
-
-    /**
-     * Map from subscriber class -> event handler method lists.  Used to determine upon registration
-     * of a new subscriber whether we need to read all the subscriber's methods again using
-     * reflection or whether we can just add the subscriber to the event type map.
-     */
-    private HashMap<Class<? extends Object>, ArrayList<EventHandlerMethod>> mSubscriberTypeMap = new HashMap<>();
-
-    /**
-     * Set of all currently registered subscribers
-     */
-    private ArrayList<Subscriber> mSubscribers = new ArrayList<>();
-
-    // For tracing
-    private int mCallCount;
-    private long mCallDurationMicros;
-
-    /**
-     * Private constructor to create an event bus for a given looper.
-     */
-    private EventBus(Looper looper) {
-        mHandler = new Handler(looper);
-    }
-
-    /**
-     * @return the default event bus for the application's main thread.
-     */
-    public static EventBus getDefault() {
-        if (sDefaultBus == null)
-        synchronized (sLock) {
-            if (sDefaultBus == null) {
-                if (DEBUG_TRACE_ALL) {
-                    logWithPid("New EventBus");
-                }
-                sDefaultBus = new EventBus(Looper.getMainLooper());
-            }
-        }
-        return sDefaultBus;
-    }
-
-    /**
-     * Registers a subscriber to receive events with the default priority.
-     *
-     * @param subscriber the subscriber to handle events.  If this is the first instance of the
-     *                   subscriber's class type that has been registered, the class's methods will
-     *                   be scanned for appropriate event handler methods.
-     */
-    public void register(Object subscriber) {
-        registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY);
-    }
-
-    /**
-     * Registers a subscriber to receive events with the given priority.
-     *
-     * @param subscriber the subscriber to handle events.  If this is the first instance of the
-     *                   subscriber's class type that has been registered, the class's methods will
-     *                   be scanned for appropriate event handler methods.
-     * @param priority the priority that this subscriber will receive events relative to other
-     *                 subscribers
-     */
-    public void register(Object subscriber, int priority) {
-        registerSubscriber(subscriber, priority);
-    }
-
-    /**
-     * Remove all EventHandlers pointing to the specified subscriber.  This does not remove the
-     * mapping of subscriber type to event handler method, in case new instances of this subscriber
-     * are registered.
-     */
-    public void unregister(Object subscriber) {
-        if (DEBUG_TRACE_ALL) {
-            logWithPid("unregister()");
-        }
-
-        // Fail immediately if we are being called from the non-main thread
-        long callingThreadId = Thread.currentThread().getId();
-        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
-            throw new RuntimeException("Can not unregister() a subscriber from a non-main thread.");
-        }
-
-        // Return early if this is not a registered subscriber
-        if (!findRegisteredSubscriber(subscriber, true /* removeFoundSubscriber */)) {
-            return;
-        }
-
-        Class<?> subscriberType = subscriber.getClass();
-        ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
-        if (subscriberMethods != null) {
-            // For each of the event handlers the subscriber handles, remove all references of that
-            // handler
-            for (EventHandlerMethod method : subscriberMethods) {
-                ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(method.eventType);
-                for (int i = eventHandlers.size() - 1; i >= 0; i--) {
-                    if (eventHandlers.get(i).subscriber.getReference() == subscriber) {
-                        eventHandlers.remove(i);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Sends an event to the subscribers of the given event type immediately.  This can only be
-     * called from the same thread as the EventBus's looper thread (for the default EventBus, this
-     * is the main application thread).
-     */
-    public void send(Event event) {
-        // Fail immediately if we are being called from the non-main thread
-        long callingThreadId = Thread.currentThread().getId();
-        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
-            throw new RuntimeException("Can not send() a message from a non-main thread.");
-        }
-
-        if (DEBUG_TRACE_ALL) {
-            logWithPid("send(" + event.getClass().getSimpleName() + ")");
-        }
-
-        // Reset the event's cancelled state
-        event.requiresPost = false;
-        event.cancelled = false;
-        queueEvent(event);
-    }
-
-    /**
-     * Post a message to the subscribers of the given event type.  The messages will be posted on
-     * the EventBus's looper thread (for the default EventBus, this is the main application thread).
-     */
-    public void post(Event event) {
-        if (DEBUG_TRACE_ALL) {
-            logWithPid("post(" + event.getClass().getSimpleName() + ")");
-        }
-
-        // Reset the event's cancelled state
-        event.requiresPost = true;
-        event.cancelled = false;
-        queueEvent(event);
-    }
-
-    /**
-     * If this method is called from the main thread, it will be handled directly. If this method
-     * is not called from the main thread, it will be posted onto the main thread.
-     */
-    public void sendOntoMainThread(Event event) {
-        long callingThreadId = Thread.currentThread().getId();
-        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
-            post(event);
-        } else {
-            send(event);
-        }
-    }
-
-    /**
-     * @return a dump of the current state of the EventBus
-     */
-    public void dump(String prefix, PrintWriter writer) {
-        writer.println(dumpInternal(prefix));
-    }
-
-    public String dumpInternal(String prefix) {
-        String innerPrefix = prefix + "  ";
-        String innerInnerPrefix = innerPrefix + "  ";
-        StringBuilder output = new StringBuilder();
-        output.append(prefix);
-        output.append("Registered class types:");
-        output.append("\n");
-        ArrayList<Class<?>> subsciberTypes = new ArrayList<>(mSubscriberTypeMap.keySet());
-        Collections.sort(subsciberTypes, new Comparator<Class<?>>() {
-            @Override
-            public int compare(Class<?> o1, Class<?> o2) {
-                return o1.getSimpleName().compareTo(o2.getSimpleName());
-            }
-        });
-        for (int i = 0; i < subsciberTypes.size(); i++) {
-            Class<?> clz = subsciberTypes.get(i);
-            output.append(innerPrefix);
-            output.append(clz.getSimpleName());
-            output.append("\n");
-        }
-        output.append(prefix);
-        output.append("Event map:");
-        output.append("\n");
-        ArrayList<Class<?>> classes = new ArrayList<>(mEventTypeMap.keySet());
-        Collections.sort(classes, new Comparator<Class<?>>() {
-            @Override
-            public int compare(Class<?> o1, Class<?> o2) {
-                return o1.getSimpleName().compareTo(o2.getSimpleName());
-            }
-        });
-        for (int i = 0; i < classes.size(); i++) {
-            Class<?> clz = classes.get(i);
-            output.append(innerPrefix);
-            output.append(clz.getSimpleName());
-            output.append(" -> ");
-            output.append("\n");
-            ArrayList<EventHandler> handlers = mEventTypeMap.get(clz);
-            for (EventHandler handler : handlers) {
-                Object subscriber = handler.subscriber.getReference();
-                if (subscriber != null) {
-                    String id = Integer.toHexString(System.identityHashCode(subscriber));
-                    output.append(innerInnerPrefix);
-                    output.append(subscriber.getClass().getSimpleName());
-                    output.append(" [0x" + id + ", #" + handler.priority + "]");
-                    output.append("\n");
-                }
-            }
-        }
-        return output.toString();
-    }
-
-    /**
-     * Registers a new subscriber.
-     */
-    private void registerSubscriber(Object subscriber, int priority) {
-        // Fail immediately if we are being called from the non-main thread
-        long callingThreadId = Thread.currentThread().getId();
-        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
-            throw new RuntimeException("Can not register() a subscriber from a non-main thread.");
-        }
-
-        // Return immediately if this exact subscriber is already registered
-        if (findRegisteredSubscriber(subscriber, false /* removeFoundSubscriber */)) {
-            return;
-        }
-
-        long t1 = 0;
-        if (DEBUG_TRACE_ALL) {
-            t1 = SystemClock.currentTimeMicro();
-            logWithPid("registerSubscriber(" + subscriber.getClass().getSimpleName() + ")");
-        }
-        Subscriber sub = new Subscriber(subscriber, SystemClock.uptimeMillis());
-        Class<?> subscriberType = subscriber.getClass();
-        ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
-        if (subscriberMethods != null) {
-            if (DEBUG_TRACE_ALL) {
-                logWithPid("Subscriber class type already registered");
-            }
-
-            // If we've parsed this subscriber type before, just add to the set for all the known
-            // events
-            for (EventHandlerMethod method : subscriberMethods) {
-                ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(method.eventType);
-                eventTypeHandlers.add(new EventHandler(sub, method, priority));
-                sortEventHandlersByPriority(eventTypeHandlers);
-            }
-            mSubscribers.add(sub);
-            return;
-        } else {
-            if (DEBUG_TRACE_ALL) {
-                logWithPid("Subscriber class type requires registration");
-            }
-
-            // If we are parsing this type from scratch, ensure we add it to the subscriber type
-            // map, and pull out he handler methods below
-            subscriberMethods = new ArrayList<>();
-            mSubscriberTypeMap.put(subscriberType, subscriberMethods);
-            mSubscribers.add(sub);
-        }
-
-        // Find all the valid event bus handler methods of the subscriber
-        Method[] methods = subscriberType.getDeclaredMethods();
-        for (Method m : methods) {
-            Class<?>[] parameterTypes = m.getParameterTypes();
-            if (isValidEventBusHandlerMethod(m, parameterTypes)) {
-                Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
-                ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(eventType);
-                if (eventTypeHandlers == null) {
-                    eventTypeHandlers = new ArrayList<>();
-                    mEventTypeMap.put(eventType, eventTypeHandlers);
-                }
-                EventHandlerMethod method = new EventHandlerMethod(m, eventType);
-                EventHandler handler = new EventHandler(sub, method, priority);
-                eventTypeHandlers.add(handler);
-                subscriberMethods.add(method);
-                sortEventHandlersByPriority(eventTypeHandlers);
-
-                if (DEBUG_TRACE_ALL) {
-                    logWithPid("  * Method: " + m.getName() +
-                            " event: " + parameterTypes[0].getSimpleName());
-                }
-            }
-        }
-        if (DEBUG_TRACE_ALL) {
-            logWithPid("Registered " + subscriber.getClass().getSimpleName() + " in " +
-                    (SystemClock.currentTimeMicro() - t1) + " microseconds");
-        }
-    }
-
-    /**
-     * Adds a new message.
-     */
-    private void queueEvent(final Event event) {
-        ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass());
-        if (eventHandlers == null) {
-            // This is just an optimization to return early if there are no handlers. However, we
-            // should still ensure that we call pre/post dispatch callbacks so that AnimatedEvents
-            // are still cleaned up correctly if a listener has not been registered to handle them
-            event.onPreDispatch();
-            event.onPostDispatch();
-            return;
-        }
-
-        // Prepare this event
-        boolean hasPostedEvent = false;
-        event.onPreDispatch();
-
-        // We need to clone the list in case a subscriber unregisters itself during traversal
-        // TODO: Investigate whether we can skip the object creation here
-        eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone();
-        int eventHandlerCount = eventHandlers.size();
-        for (int i = 0; i < eventHandlerCount; i++) {
-            final EventHandler eventHandler = eventHandlers.get(i);
-            if (eventHandler.subscriber.getReference() != null) {
-                if (event.requiresPost) {
-                    mHandler.post(() -> processEvent(eventHandler, event));
-                    hasPostedEvent = true;
-                } else {
-                    processEvent(eventHandler, event);
-                }
-            }
-        }
-
-        // Clean up after this event, deferring until all subscribers have been called
-        if (hasPostedEvent) {
-            mHandler.post(event::onPostDispatch);
-        } else {
-            event.onPostDispatch();
-        }
-    }
-
-    /**
-     * Processes and dispatches the given event to the given event handler, on the thread of whoever
-     * calls this method.
-     */
-    private void processEvent(final EventHandler eventHandler, final Event event) {
-        // Skip if the event was already cancelled
-        if (event.cancelled) {
-            if (event.trace || DEBUG_TRACE_ALL) {
-                logWithPid("Event dispatch cancelled");
-            }
-            return;
-        }
-
-        try {
-            if (event.trace || DEBUG_TRACE_ALL) {
-                logWithPid(" -> " + eventHandler.toString());
-            }
-            Object sub = eventHandler.subscriber.getReference();
-            if (sub != null) {
-                long t1 = 0;
-                if (DEBUG_TRACE_ALL) {
-                    t1 = SystemClock.currentTimeMicro();
-                }
-                eventHandler.method.invoke(sub, event);
-                if (DEBUG_TRACE_ALL) {
-                    long duration = (SystemClock.currentTimeMicro() - t1);
-                    mCallDurationMicros += duration;
-                    mCallCount++;
-                    logWithPid(eventHandler.method.toString() + " duration: " + duration +
-                            " microseconds, avg: " + (mCallDurationMicros / mCallCount));
-                }
-            } else {
-                Log.e(TAG, "Failed to deliver event to null subscriber");
-            }
-        } catch (IllegalAccessException e) {
-            Log.e(TAG, "Failed to invoke method", e.getCause());
-        } catch (InvocationTargetException e) {
-            throw new RuntimeException(e.getCause());
-        }
-    }
-
-    /**
-     * Returns whether this subscriber is currently registered.  If {@param removeFoundSubscriber}
-     * is true, then remove the subscriber before returning.
-     */
-    private boolean findRegisteredSubscriber(Object subscriber, boolean removeFoundSubscriber) {
-        for (int i = mSubscribers.size() - 1; i >= 0; i--) {
-            Subscriber sub = mSubscribers.get(i);
-            if (sub.getReference() == subscriber) {
-                if (removeFoundSubscriber) {
-                    mSubscribers.remove(i);
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return whether {@param method} is a valid (normal or interprocess) event bus handler method
-     */
-    private boolean isValidEventBusHandlerMethod(Method method, Class<?>[] parameterTypes) {
-        int modifiers = method.getModifiers();
-        if (Modifier.isPublic(modifiers) &&
-                Modifier.isFinal(modifiers) &&
-                method.getReturnType().equals(Void.TYPE) &&
-                parameterTypes.length == 1) {
-            if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) &&
-                            method.getName().startsWith(METHOD_PREFIX)) {
-                return true;
-            } else {
-                if (DEBUG_TRACE_ALL) {
-                    if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) {
-                        logWithPid("  Expected method take an Event-based parameter: " + method.getName());
-                    }
-                }
-            }
-        } else {
-            if (DEBUG_TRACE_ALL) {
-                if (!Modifier.isPublic(modifiers)) {
-                    logWithPid("  Expected method to be public: " + method.getName());
-                } else if (!Modifier.isFinal(modifiers)) {
-                    logWithPid("  Expected method to be final: " + method.getName());
-                } else if (!method.getReturnType().equals(Void.TYPE)) {
-                    logWithPid("  Expected method to return null: " + method.getName());
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Sorts the event handlers by priority and registration time.
-     */
-    private void sortEventHandlersByPriority(List<EventHandler> eventHandlers) {
-        Collections.sort(eventHandlers, EVENT_HANDLER_COMPARATOR);
-    }
-
-    /**
-     * Helper method to log the given {@param text} with the current process and user id.
-     */
-    private static void logWithPid(String text) {
-        Log.d(TAG, "[" + android.os.Process.myPid() + ", u" + UserHandle.myUserId() + "] " + text);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java
deleted file mode 100644
index 4738eed..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/AppTransitionFinishedEvent.java
+++ /dev/null
@@ -1,25 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Sent when an app transition has finished playing.
- */
-public class AppTransitionFinishedEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
deleted file mode 100644
index fec34e3..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-
-/**
- * This is sent when we want to cancel the enter-recents window animation for the launch task.
- */
-public class CancelEnterRecentsWindowAnimationEvent extends EventBus.Event {
-
-    // This is set for the task that is launching, which allows us to ensure that we are not
-    // cancelling the same task animation (it will just be overwritten instead)
-    public final Task launchTask;
-
-    public CancelEnterRecentsWindowAnimationEvent(Task launchTask) {
-        this.launchTask = launchTask;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
deleted file mode 100644
index 294c1e7..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
+++ /dev/null
@@ -1,38 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the Recents activity configuration has changed.
- */
-public class ConfigurationChangedEvent extends EventBus.AnimatedEvent {
-
-    public final boolean fromMultiWindow;
-    public final boolean fromDeviceOrientationChange;
-    public final boolean fromDisplayDensityChange;
-    public final boolean hasStackTasks;
-
-    public ConfigurationChangedEvent(boolean fromMultiWindow, boolean fromDeviceOrientationChange,
-            boolean fromDisplayDensityChange, boolean hasStackTasks) {
-        this.fromMultiWindow = fromMultiWindow;
-        this.fromDeviceOrientationChange = fromDeviceOrientationChange;
-        this.fromDisplayDensityChange = fromDisplayDensityChange;
-        this.hasStackTasks = hasStackTasks;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
deleted file mode 100644
index e7be858..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DismissRecentsToHomeAnimationStarted.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the task animation when dismissing Recents starts.
- */
-public class DismissRecentsToHomeAnimationStarted extends EventBus.AnimatedEvent {
-
-    public final boolean animated;
-
-    public DismissRecentsToHomeAnimationStarted(boolean animated) {
-        this.animated = animated;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java
deleted file mode 100644
index 32d9a70..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedFirstAnimationFrameEvent.java
+++ /dev/null
@@ -1,25 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus.Event;
-
-/**
- * Sent when the window animation has started when docking a task
- */
-public class DockedFirstAnimationFrameEvent extends Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedTopTaskEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedTopTaskEvent.java
deleted file mode 100644
index 9e3ced3..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/DockedTopTaskEvent.java
+++ /dev/null
@@ -1,34 +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.recents.events.activity;
-
-import android.graphics.Rect;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Fires when the user invoked the gesture to dock the top/left task after we called into window
- * manager and before we start recents.
- */
-public class DockedTopTaskEvent extends EventBus.Event {
-
-    public Rect initialRect;
-
-    public DockedTopTaskEvent(Rect initialRect) {
-        this.initialRect = initialRect;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
deleted file mode 100644
index b31f320..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowAnimationCompletedEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the window animation into Recents completes.  We use this signal to know when
- * we can start in-app animations so that they don't conflict with the window transition into
- * Recents.
- */
-public class EnterRecentsWindowAnimationCompletedEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowLastAnimationFrameEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowLastAnimationFrameEvent.java
deleted file mode 100644
index fd023d8..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/EnterRecentsWindowLastAnimationFrameEvent.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-public class EnterRecentsWindowLastAnimationFrameEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ExitRecentsWindowFirstAnimationFrameEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ExitRecentsWindowFirstAnimationFrameEvent.java
deleted file mode 100644
index fa806eb..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ExitRecentsWindowFirstAnimationFrameEvent.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Event sent when the exit animation is started.
- *
- * This is sent so parts of UI can synchronize on this event and adjust their appearance. An example
- * of that is hiding the tasks when the launched application window becomes visible.
- */
-public class ExitRecentsWindowFirstAnimationFrameEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
deleted file mode 100644
index bf9b421..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideRecentsEvent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the user taps on the Home button or finishes alt-tabbing to hide the Recents
- * activity.
- */
-public class HideRecentsEvent extends EventBus.Event {
-
-    public final boolean triggeredFromAltTab;
-    public final boolean triggeredFromHomeKey;
-
-    public HideRecentsEvent(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-        this.triggeredFromAltTab = triggeredFromAltTab;
-        this.triggeredFromHomeKey = triggeredFromHomeKey;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
deleted file mode 100644
index e4a4f59..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
+++ /dev/null
@@ -1,36 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the stack action button should be hidden.
- */
-public class HideStackActionButtonEvent extends EventBus.Event {
-
-    // Whether or not to translate the stack action button when hiding it
-    public final boolean translate;
-
-    public HideStackActionButtonEvent() {
-        this(true);
-    }
-
-    public HideStackActionButtonEvent(boolean translate) {
-        this.translate = translate;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchMostRecentTaskRequestEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchMostRecentTaskRequestEvent.java
deleted file mode 100644
index 24913a4..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchMostRecentTaskRequestEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This event is sent to request that the most recent task is launched.
- */
-public class LaunchMostRecentTaskRequestEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchNextTaskRequestEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchNextTaskRequestEvent.java
deleted file mode 100644
index 11604b5..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchNextTaskRequestEvent.java
+++ /dev/null
@@ -1,27 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This event is sent to request that the next task is launched after a double-tap on the Recents
- * button.
- */
-public class LaunchNextTaskRequestEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
deleted file mode 100644
index 2409f39..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.graphics.Rect;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent to request that a particular task is launched.
- */
-public class LaunchTaskEvent extends EventBus.Event {
-
-    public final TaskView taskView;
-    public final Task task;
-    public final Rect targetTaskBounds;
-    public final int targetWindowingMode;
-    public final int targetActivityType;
-    public final boolean screenPinningRequested;
-
-    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
-            boolean screenPinningRequested) {
-        this(taskView, task, targetTaskBounds, screenPinningRequested,
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED);
-    }
-
-    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
-            boolean screenPinningRequested, int windowingMode, int activityType) {
-        this.taskView = taskView;
-        this.task = task;
-        this.targetTaskBounds = targetTaskBounds;
-        this.targetWindowingMode = windowingMode;
-        this.targetActivityType = activityType;
-        this.screenPinningRequested = screenPinningRequested;
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
deleted file mode 100644
index 3a2d58c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when we fail to launch a task.
- */
-public class LaunchTaskFailedEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskStartedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskStartedEvent.java
deleted file mode 100644
index 3925ab1..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskStartedEvent.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent following {@link LaunchTaskEvent} after the call to the system is made to
- * start the task.
- */
-public class LaunchTaskStartedEvent extends EventBus.AnimatedEvent {
-
-    public final TaskView taskView;
-    public final boolean screenPinningRequested;
-
-    public LaunchTaskStartedEvent(TaskView taskView, boolean screenPinningRequested) {
-        this.taskView = taskView;
-        this.screenPinningRequested = screenPinningRequested;
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
deleted file mode 100644
index ec5089f..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when we successfully launch a task.
- */
-public class LaunchTaskSucceededEvent extends EventBus.Event {
-
-    public final int taskIndexFromStackFront;
-
-    public LaunchTaskSucceededEvent(int taskIndexFromStackFront) {
-        this.taskIndexFromStackFront = taskIndexFromStackFront;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
deleted file mode 100644
index 64eeafa..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
-
-/**
- * This is sent by the activity whenever the multi-window state has changed.
- */
-public class MultiWindowStateChangedEvent extends EventBus.AnimatedEvent {
-
-    public final boolean inMultiWindow;
-    // This flag is only used when undocking a task
-    public final boolean showDeferredAnimation;
-    public final TaskStack stack;
-
-    public MultiWindowStateChangedEvent(boolean inMultiWindow, boolean showDeferredAnimation,
-            TaskStack stack) {
-        this.inMultiWindow = inMultiWindow;
-        this.showDeferredAnimation = showDeferredAnimation;
-        this.stack = stack;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
deleted file mode 100644
index 47670e0..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.RecentsActivity;
-
-/**
- * This event is sent by {@link RecentsActivity} when a package on the the system changes.
- * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
- * packages.
- */
-public class PackagesChangedEvent extends EventBus.Event {
-
-    public final String packageName;
-    public final int userId;
-
-    public PackagesChangedEvent(String packageName, int userId) {
-        this.packageName = packageName;
-        this.userId = userId;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/RecentsActivityStartingEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/RecentsActivityStartingEvent.java
deleted file mode 100644
index a2ecfe2..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/RecentsActivityStartingEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Called after recents activity is being started, i.e. startActivity has just been called.
- */
-public class RecentsActivityStartingEvent extends EventBus.Event{
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java
deleted file mode 100644
index 75bfd7b..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java
+++ /dev/null
@@ -1,25 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Sent when the stack should be hidden and the empty view shown.
- */
-public class ShowEmptyViewEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java
deleted file mode 100644
index d81f89c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ShowStackActionButtonEvent.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the stack action view button should be shown.
- */
-public class ShowStackActionButtonEvent extends EventBus.Event {
-
-    // Whether or not to translate the stack action button when showing it
-    public final boolean translate;
-
-    public ShowStackActionButtonEvent(boolean translate) {
-        this.translate = translate;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
deleted file mode 100644
index 0d614e8c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
-
-/**
- * This is sent by the activity whenever the task stach has changed.
- */
-public class TaskStackUpdatedEvent extends EventBus.AnimatedEvent {
-
-    /**
-     * A new TaskStack instance representing the latest stack state.
-     */
-    public final TaskStack stack;
-    public final boolean inMultiWindow;
-
-    public TaskStackUpdatedEvent(TaskStack stack, boolean inMultiWindow) {
-        this.stack = stack;
-        this.inMultiWindow = inMultiWindow;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
deleted file mode 100644
index 49655b4..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/ToggleRecentsEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the user taps on the Overview button to toggle the Recents activity.
- */
-public class ToggleRecentsEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/UndockingTaskEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/UndockingTaskEvent.java
deleted file mode 100644
index d5083a8..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/activity/UndockingTaskEvent.java
+++ /dev/null
@@ -1,28 +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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Fires when the user invoked the gesture to undock the task in the docked stack.
- */
-public class UndockingTaskEvent extends EventBus.Event {
-
-    public UndockingTaskEvent() {
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java
deleted file mode 100644
index f4d2fcf..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java
+++ /dev/null
@@ -1,31 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when an activity is pinned.
- */
-public class ActivityPinnedEvent extends EventBus.Event {
-
-    public final int taskId;
-
-    public ActivityPinnedEvent(int taskId) {
-        this.taskId = taskId;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java
deleted file mode 100644
index 48c5f0b..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java
+++ /dev/null
@@ -1,25 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when an activity is unpinned.
- */
-public class ActivityUnpinnedEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
deleted file mode 100644
index 37266f6..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
+++ /dev/null
@@ -1,25 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the PiP should be expanded due to being relaunched.
- */
-public class ExpandPipEvent extends EventBus.Event {
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java
deleted file mode 100644
index ce4f207..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the PiP menu should be hidden.
- */
-public class HidePipMenuEvent extends EventBus.AnimatedEvent {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
deleted file mode 100644
index 8843eb4..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.component;
-
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the visibility of the RecentsActivity for the current user changes.  Handlers
- * of this event should not alter the UI, as the activity may still be visible.
- */
-public class RecentsVisibilityChangedEvent extends EventBus.Event {
-
-    public final Context applicationContext;
-    public final boolean visible;
-
-    public RecentsVisibilityChangedEvent(Context context, boolean visible) {
-        this.applicationContext = context.getApplicationContext();
-        this.visible = visible;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
deleted file mode 100644
index d460917..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.component;
-
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when we want to start screen pinning.
- */
-public class ScreenPinningRequestEvent extends EventBus.Event {
-
-    public final Context applicationContext;
-    public final int taskId;
-
-    public ScreenPinningRequestEvent(Context context, int taskId) {
-        this.applicationContext = context.getApplicationContext();
-        this.taskId = taskId;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
deleted file mode 100644
index d9cf5fb..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/SetWaitingForTransitionStartEvent.java
+++ /dev/null
@@ -1,31 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when we are setting/resetting the flag to wait for the transition to start.
- */
-public class SetWaitingForTransitionStartEvent extends EventBus.Event {
-
-    public final boolean waitingForTransitionStart;
-
-    public SetWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
-        this.waitingForTransitionStart = waitingForTransitionStart;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java
deleted file mode 100644
index e2b39c3..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/component/ShowUserToastEvent.java
+++ /dev/null
@@ -1,33 +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.recents.events.component;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when we want to show a toast for the current user.
- */
-public class ShowUserToastEvent extends EventBus.Event {
-
-    public final int msgResId;
-    public final int msgLength;
-
-    public ShowUserToastEvent(int msgResId, int msgLength) {
-        this.msgResId = msgResId;
-        this.msgLength = msgLength;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
deleted file mode 100644
index 0352161..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent whenever all the task views in a stack have been dismissed.
- */
-public class AllTaskViewsDismissedEvent extends EventBus.Event {
-
-    public final int msgResId;
-
-    public AllTaskViewsDismissedEvent(int msgResId) {
-        this.msgResId = msgResId;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
deleted file mode 100644
index b52e83b..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-
-/**
- * This is sent when the data associated with a given {@link Task} should be deleted from the
- * system.
- */
-public class DeleteTaskDataEvent extends EventBus.Event {
-
-    public final Task task;
-
-    public DeleteTaskDataEvent(Task task) {
-        this.task = task;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
deleted file mode 100644
index f8b59c7..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissAllTaskViewsEvent.java
+++ /dev/null
@@ -1,27 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent to request that all the {@link TaskView}s are dismissed.
- */
-public class DismissAllTaskViewsEvent extends EventBus.AnimatedEvent {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
deleted file mode 100644
index 1f8c644..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent to request that the given {@link TaskView} is dismissed.
- */
-public class DismissTaskViewEvent extends EventBus.AnimatedEvent {
-
-    public final TaskView taskView;
-
-    public DismissTaskViewEvent(TaskView taskView) {
-        this.taskView = taskView;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
deleted file mode 100644
index 9be8eb1..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus.Event;
-
-/**
- * This event is sent when the user finished dragging in recents.
- */
-public class DraggingInRecentsEndedEvent extends Event {
-
-    public final float velocity;
-
-    public DraggingInRecentsEndedEvent(float velocity) {
-        this.velocity = velocity;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
deleted file mode 100644
index 5e8bfd4..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus.Event;
-
-/**
- * This event is sent when the user changed how far they are dragging in recents.
- */
-public class DraggingInRecentsEvent extends Event {
-
-    public final float distanceFromTop;
-
-    public DraggingInRecentsEvent(float distanceFromTop) {
-        this.distanceFromTop = distanceFromTop;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java
deleted file mode 100644
index d6ef636..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/HideIncompatibleAppOverlayEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when a user stops draggin an incompatible app task.
- */
-public class HideIncompatibleAppOverlayEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsDrawnEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsDrawnEvent.java
deleted file mode 100644
index 5483166..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsDrawnEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Fired when recents was launched and has drawn its first frame.
- */
-public class RecentsDrawnEvent extends EventBus.Event {
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsGrowingEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsGrowingEvent.java
deleted file mode 100644
index d9b0027..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/RecentsGrowingEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Sent when recents is about to grow in multi-window mode when entering recents.
- */
-public class RecentsGrowingEvent extends EventBus.Event {
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
deleted file mode 100644
index da19384..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-
-/**
- * This is sent when a user wants to show the application info for a {@link Task}.
- */
-public class ShowApplicationInfoEvent extends EventBus.Event {
-
-    public final Task task;
-
-    public ShowApplicationInfoEvent(Task task) {
-        this.task = task;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java
deleted file mode 100644
index 3a4350e..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/ShowIncompatibleAppOverlayEvent.java
+++ /dev/null
@@ -1,26 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when a user starts dragging an incompatible app task.
- */
-public class ShowIncompatibleAppOverlayEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
deleted file mode 100644
index c4b47c0..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import android.util.MutableInt;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent whenever a new scroll gesture happens on a stack view.
- */
-public class StackViewScrolledEvent extends EventBus.ReusableEvent {
-
-    public final MutableInt yMovement;
-
-    public StackViewScrolledEvent() {
-        yMovement = new MutableInt(0);
-    }
-
-    public void updateY(int y) {
-        yMovement.value = y;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
deleted file mode 100644
index f082928..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
+++ /dev/null
@@ -1,34 +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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-/**
- * Sent when a task snapshot has changed.
- */
-public class TaskSnapshotChangedEvent extends EventBus.Event {
-
-    public final int taskId;
-    public final ThumbnailData thumbnailData;
-
-    public TaskSnapshotChangedEvent(int taskId, ThumbnailData thumbnailData) {
-        this.taskId = taskId;
-        this.thumbnailData = thumbnailData;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
deleted file mode 100644
index 9738124..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent when a {@link TaskView} has been dismissed and is no longer visible.
- */
-public class TaskViewDismissedEvent extends EventBus.Event {
-
-    public final Task task;
-    public final TaskView taskView;
-    public final AnimationProps animation;
-
-    public TaskViewDismissedEvent(Task task, TaskView taskView, AnimationProps animation) {
-        this.task = task;
-        this.taskView = taskView;
-        this.animation = animation;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
deleted file mode 100644
index 39e4c1d..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/UserInteractionEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent whenever the user interacts with the activity.
- */
-public class UserInteractionEvent extends EventBus.ReusableEvent {
-    // Simple Event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
deleted file mode 100644
index cf61b1e..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.dragndrop;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.DropTarget;
-
-/**
- * This event is sent when a user drags in/out of a drop target.
- */
-public class DragDropTargetChangedEvent extends EventBus.AnimatedEvent {
-
-    // The task that is currently being dragged
-    public final Task task;
-    public final DropTarget dropTarget;
-
-    public DragDropTargetChangedEvent(Task task, DropTarget dropTarget) {
-        this.task = task;
-        this.dropTarget = dropTarget;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
deleted file mode 100644
index c11936e..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
+++ /dev/null
@@ -1,38 +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.recents.events.ui.dragndrop;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent whenever a drag end is cancelled because of an error.
- */
-public class DragEndCancelledEvent extends EventBus.AnimatedEvent {
-
-    public final TaskStack stack;
-    public final Task task;
-    public final TaskView taskView;
-
-    public DragEndCancelledEvent(TaskStack stack, Task task, TaskView taskView) {
-        this.stack = stack;
-        this.task = task;
-        this.taskView = taskView;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
deleted file mode 100644
index 73cbde9..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.dragndrop;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent whenever a drag ends.
- */
-public class DragEndEvent extends EventBus.AnimatedEvent {
-
-    public final Task task;
-    public final TaskView taskView;
-    public final DropTarget dropTarget;
-
-    public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget) {
-        this.task = task;
-        this.taskView = taskView;
-        this.dropTarget = dropTarget;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
deleted file mode 100644
index 021be77..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.dragndrop;
-
-import android.graphics.Point;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent whenever a drag starts.
- */
-public class DragStartEvent extends EventBus.Event {
-
-    public final Task task;
-    public final TaskView taskView;
-    public final Point tlOffset;
-    public final boolean isUserTouchInitiated;
-
-    public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
-        this(task, taskView, tlOffset, true);
-    }
-
-    public DragStartEvent(Task task, TaskView taskView, Point tlOffset,
-            boolean isUserTouchInitiated) {
-        this.task = task;
-        this.taskView = taskView;
-        this.tlOffset = tlOffset;
-        this.isUserTouchInitiated = isUserTouchInitiated;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
deleted file mode 100644
index 64ba574..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.dragndrop;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.RecentsViewTouchHandler;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent by the drag manager when it requires drop targets to register themselves for
- * the current drag gesture.
- */
-public class DragStartInitializeDropTargetsEvent extends EventBus.Event {
-
-    public final Task task;
-    public final TaskView taskView;
-    public final RecentsViewTouchHandler handler;
-
-    public DragStartInitializeDropTargetsEvent(Task task, TaskView taskView,
-            RecentsViewTouchHandler handler) {
-        this.task = task;
-        this.taskView = taskView;
-        this.handler = handler;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
deleted file mode 100644
index df74018..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.focus;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.views.TaskView;
-
-/**
- * This event is sent to request that the currently focused {@link TaskView} is dismissed.
- */
-public class DismissFocusedTaskViewEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
deleted file mode 100644
index 171ab5e..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.focus;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Focuses the next task view in the stack.
- */
-public class FocusNextTaskViewEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
deleted file mode 100644
index 22469e7..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.events.ui.focus;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Focuses the previous task view in the stack.
- */
-public class FocusPreviousTaskViewEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.java
deleted file mode 100644
index 5508d26..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/events/ui/focus/NavigateTaskViewEvent.java
+++ /dev/null
@@ -1,49 +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.recents.events.ui.focus;
-
-import android.view.KeyEvent;
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * Navigates the task view by arrow keys.
- */
-public class NavigateTaskViewEvent extends EventBus.Event {
-    public enum Direction {
-        UNDEFINED, UP, DOWN, LEFT, RIGHT;
-    }
-
-    public Direction direction;
-    public NavigateTaskViewEvent(Direction direction) {
-        this.direction = direction;
-    }
-
-    public static Direction getDirectionFromKeyCode(int keyCode) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_UP:
-                return Direction.UP;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                return Direction.DOWN;
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                return Direction.LEFT;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                return Direction.RIGHT;
-            default:
-                return Direction.UNDEFINED;
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/DozeTrigger.java
deleted file mode 100644
index 574ea03..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.os.Handler;
-import android.view.ViewDebug;
-
-/**
- * A dozer is a class that fires a trigger after it falls asleep.
- * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched.
- */
-public class DozeTrigger {
-
-    Handler mHandler;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    boolean mIsDozing;
-    @ViewDebug.ExportedProperty(category="recents")
-    boolean mIsAsleep;
-    @ViewDebug.ExportedProperty(category="recents")
-    int mDozeDurationMilliseconds;
-    Runnable mOnSleepRunnable;
-
-    // Sleep-runnable
-    Runnable mDozeRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mIsDozing = false;
-            mIsAsleep = true;
-            mOnSleepRunnable.run();
-        }
-    };
-
-    public DozeTrigger(int dozeDurationMilliseconds, Runnable onSleepRunnable) {
-        mHandler = new Handler();
-        mDozeDurationMilliseconds = dozeDurationMilliseconds;
-        mOnSleepRunnable = onSleepRunnable;
-    }
-
-    /**
-     * Starts dozing and queues the onSleepRunnable to be called. This also resets the trigger flag.
-     */
-    public void startDozing() {
-        forcePoke();
-        mIsAsleep = false;
-    }
-
-    /**
-     * Stops dozing and prevents the onSleepRunnable from being called.
-     */
-    public void stopDozing() {
-        mHandler.removeCallbacks(mDozeRunnable);
-        mIsDozing = false;
-        mIsAsleep = false;
-    }
-
-    /**
-     * Updates the duration that we have to wait until dozing triggers.
-     */
-    public void setDozeDuration(int duration) {
-        mDozeDurationMilliseconds = duration;
-    }
-
-    /**
-     * Poke this dozer to wake it up if it is dozing, delaying the onSleepRunnable from being
-     * called for a for the doze duration.
-     */
-    public void poke() {
-        if (mIsDozing) {
-            forcePoke();
-        }
-    }
-
-    /**
-     * Poke this dozer to wake it up even if it is not currently dozing.
-     */
-    void forcePoke() {
-        mHandler.removeCallbacks(mDozeRunnable);
-        mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds);
-        mIsDozing = true;
-    }
-
-    /** Returns whether we are dozing or not. */
-    public boolean isDozing() {
-        return mIsDozing;
-    }
-
-    /** Returns whether the trigger has fired at least once. */
-    public boolean isAsleep() {
-        return mIsAsleep;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/FreePathInterpolator.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/FreePathInterpolator.java
deleted file mode 100644
index 720c952..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/FreePathInterpolator.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.graphics.Path;
-import android.view.animation.BaseInterpolator;
-import android.view.animation.Interpolator;
-
-/**
- * An interpolator that can traverse a Path. The x coordinate along the <code>Path</code>
- * is the input value and the output is the y coordinate of the line at that point.
- * This means that the Path must conform to a function <code>y = f(x)</code>.
- *
- * <p>The <code>Path</code> must not have gaps in the x direction and must not
- * loop back on itself such that there can be two points sharing the same x coordinate.
- * It is alright to have a disjoint line in the vertical direction:</p>
- * <p><blockquote><pre>
- *     Path path = new Path();
- *     path.lineTo(0.25f, 0.25f);
- *     path.moveTo(0.25f, 0.5f);
- *     path.lineTo(1f, 1f);
- * </pre></blockquote></p>
- */
-public class FreePathInterpolator extends BaseInterpolator {
-
-    // This governs how accurate the approximation of the Path is.
-    private static final float PRECISION = 0.002f;
-
-    private float[] mX;
-    private float[] mY;
-    private float mArcLength;
-
-    /**
-     * Create an interpolator for an arbitrary <code>Path</code>.
-     *
-     * @param path The <code>Path</code> to use to make the line representing the interpolator.
-     */
-    public FreePathInterpolator(Path path) {
-        initPath(path);
-    }
-
-    private void initPath(Path path) {
-        float[] pointComponents = path.approximate(PRECISION);
-
-        int numPoints = pointComponents.length / 3;
-
-        mX = new float[numPoints];
-        mY = new float[numPoints];
-        mArcLength = 0;
-        float prevX = 0;
-        float prevY = 0;
-        float prevFraction = 0;
-        int componentIndex = 0;
-        for (int i = 0; i < numPoints; i++) {
-            float fraction = pointComponents[componentIndex++];
-            float x = pointComponents[componentIndex++];
-            float y = pointComponents[componentIndex++];
-            if (fraction == prevFraction && x != prevX) {
-                throw new IllegalArgumentException(
-                        "The Path cannot have discontinuity in the X axis.");
-            }
-            if (x < prevX) {
-                throw new IllegalArgumentException("The Path cannot loop back on itself.");
-            }
-            mX[i] = x;
-            mY[i] = y;
-            mArcLength += Math.hypot(x - prevX, y - prevY);
-            prevX = x;
-            prevY = y;
-            prevFraction = fraction;
-        }
-    }
-
-    /**
-     * Using the line in the Path in this interpolator that can be described as
-     * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
-     * as the x coordinate.
-     *
-     * @param t Treated as the x coordinate along the line.
-     * @return The y coordinate of the Path along the line where x = <code>t</code>.
-     * @see Interpolator#getInterpolation(float)
-     */
-    @Override
-    public float getInterpolation(float t) {
-        int startIndex = 0;
-        int endIndex = mX.length - 1;
-
-        // Return early if out of bounds
-        if (t <= 0) {
-            return mY[startIndex];
-        } else if (t >= 1) {
-            return mY[endIndex];
-        }
-
-        // Do a binary search for the correct x to interpolate between.
-        while (endIndex - startIndex > 1) {
-            int midIndex = (startIndex + endIndex) / 2;
-            if (t < mX[midIndex]) {
-                endIndex = midIndex;
-            } else {
-                startIndex = midIndex;
-            }
-        }
-
-        float xRange = mX[endIndex] - mX[startIndex];
-        if (xRange == 0) {
-            return mY[startIndex];
-        }
-
-        float tInRange = t - mX[startIndex];
-        float fraction = tInRange / xRange;
-
-        float startY = mY[startIndex];
-        float endY = mY[endIndex];
-        return startY + (fraction * (endY - startY));
-    }
-
-    /**
-     * Finds the x that provides the given <code>y = f(x)</code>.
-     *
-     * @param y a value from (0,1) that is in this path.
-     */
-    public float getX(float y) {
-        int startIndex = 0;
-        int endIndex = mY.length - 1;
-
-        // Return early if out of bounds
-        if (y <= 0) {
-            return mX[endIndex];
-        } else if (y >= 1) {
-            return mX[startIndex];
-        }
-
-        // Do a binary search for index that bounds the y
-        while (endIndex - startIndex > 1) {
-            int midIndex = (startIndex + endIndex) / 2;
-            if (y < mY[midIndex]) {
-                startIndex = midIndex;
-            } else {
-                endIndex = midIndex;
-            }
-        }
-
-        float yRange = mY[endIndex] - mY[startIndex];
-        if (yRange == 0) {
-            return mX[startIndex];
-        }
-
-        float tInRange = y - mY[startIndex];
-        float fraction = tInRange / yRange;
-
-        float startX = mX[startIndex];
-        float endX = mX[endIndex];
-        return startX + (fraction * (endX - startX));
-    }
-
-    /**
-     * Returns the arclength of the path we are interpolating.
-     */
-    public float getArcLength() {
-        return mArcLength;
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
deleted file mode 100644
index 2637d88..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-
-import java.util.ArrayList;
-
-/**
- * A ref counted trigger that does some logic when the count is first incremented, or last
- * decremented.  Not thread safe as it's not currently needed.
- */
-public class ReferenceCountedTrigger {
-
-    int mCount;
-    ArrayList<Runnable> mFirstIncRunnables = new ArrayList<>();
-    ArrayList<Runnable> mLastDecRunnables = new ArrayList<>();
-    Runnable mErrorRunnable;
-
-    // Convenience runnables
-    Runnable mIncrementRunnable = new Runnable() {
-        @Override
-        public void run() {
-            increment();
-        }
-    };
-    Runnable mDecrementRunnable = new Runnable() {
-        @Override
-        public void run() {
-            decrement();
-        }
-    };
-
-    public ReferenceCountedTrigger() {
-        this(null, null, null);
-    }
-
-    public ReferenceCountedTrigger(Runnable firstIncRunnable, Runnable lastDecRunnable,
-            Runnable errorRunanable) {
-        if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable);
-        if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable);
-        mErrorRunnable = errorRunanable;
-    }
-
-    /** Increments the ref count */
-    public void increment() {
-        if (mCount == 0 && !mFirstIncRunnables.isEmpty()) {
-            int numRunnables = mFirstIncRunnables.size();
-            for (int i = 0; i < numRunnables; i++) {
-                mFirstIncRunnables.get(i).run();
-            }
-        }
-        mCount++;
-    }
-
-    /** Convenience method to increment this trigger as a runnable */
-    public Runnable incrementAsRunnable() {
-        return mIncrementRunnable;
-    }
-
-    /** Adds a runnable to the last-decrement runnables list. */
-    public void addLastDecrementRunnable(Runnable r) {
-        mLastDecRunnables.add(r);
-    }
-
-    /** Decrements the ref count */
-    public void decrement() {
-        mCount--;
-        if (mCount == 0) {
-            flushLastDecrementRunnables();
-        } else if (mCount < 0) {
-            if (mErrorRunnable != null) {
-                mErrorRunnable.run();
-            } else {
-                throw new RuntimeException("Invalid ref count");
-            }
-        }
-    }
-
-    /**
-     * Runs and clears all the last-decrement runnables now.
-     */
-    public void flushLastDecrementRunnables() {
-        if (!mLastDecRunnables.isEmpty()) {
-            int numRunnables = mLastDecRunnables.size();
-            for (int i = 0; i < numRunnables; i++) {
-                mLastDecRunnables.get(i).run();
-            }
-        }
-        mLastDecRunnables.clear();
-    }
-
-    /**
-     * Convenience method to decrement this trigger as a animator listener.  This listener is
-     * guarded to prevent being called back multiple times, and will trigger a decrement once and
-     * only once.
-     */
-    public Animator.AnimatorListener decrementOnAnimationEnd() {
-        return new AnimatorListenerAdapter() {
-            private boolean hasEnded;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (hasEnded) return;
-                decrement();
-                hasEnded = true;
-            }
-        };
-    }
-
-    /** Returns the current ref count */
-    public int getCount() {
-        return mCount;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
deleted file mode 100644
index 5d7f1ba..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
+++ /dev/null
@@ -1,35 +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.recents.misc;
-
-import android.content.Context;
-
-import com.android.systemui.shared.system.TaskStackChangeListener;
-
-/**
- * An implementation of {@link TaskStackChangeListener}.
- */
-public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
-
-    /**
-     * Checks that the current user matches the user's SystemUI process.
-     */
-    protected final boolean checkCurrentUserId(Context context, boolean debug) {
-        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
-        return checkCurrentUserId(currentUserId, debug);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SystemServicesProxy.java
deleted file mode 100644
index 44354bc1..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.StackInfo;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.AppGlobals;
-import android.app.IActivityManager;
-import android.app.IActivityTaskManager;
-import android.app.WindowConfiguration;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.service.dreams.DreamService;
-import android.service.dreams.IDreamManager;
-import android.util.Log;
-import android.util.MutableBoolean;
-import android.view.Display;
-import android.view.IDockedStackListener;
-import android.view.IWindowManager;
-import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.app.AssistUtils;
-import com.android.internal.os.BackgroundThread;
-import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
-
-import java.util.List;
-
-/**
- * Acts as a shim around the real system services that we need to access data from, and provides
- * a point of injection when testing UI.
- */
-public class SystemServicesProxy {
-    final static String TAG = "SystemServicesProxy";
-
-    final static BitmapFactory.Options sBitmapOptions;
-    static {
-        sBitmapOptions = new BitmapFactory.Options();
-        sBitmapOptions.inMutable = true;
-        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
-    }
-
-    private static SystemServicesProxy sSystemServicesProxy;
-
-    AccessibilityManager mAccm;
-    ActivityManager mAm;
-    IActivityManager mIam;
-    IActivityTaskManager mIatm;
-    PackageManager mPm;
-    IPackageManager mIpm;
-    private final IDreamManager mDreamManager;
-    private final Context mContext;
-    AssistUtils mAssistUtils;
-    WindowManager mWm;
-    IWindowManager mIwm;
-    UserManager mUm;
-    Display mDisplay;
-    String mRecentsPackage;
-    private int mCurrentUserId;
-
-    boolean mIsSafeMode;
-
-    int mDummyThumbnailWidth;
-    int mDummyThumbnailHeight;
-    Paint mBgProtectionPaint;
-    Canvas mBgProtectionCanvas;
-
-    private final Runnable mGcRunnable = new Runnable() {
-        @Override
-        public void run() {
-            System.gc();
-            System.runFinalization();
-        }
-    };
-
-    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
-
-    private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
-            (String name, Drawable picture, String userAccount) ->
-                    mCurrentUserId = mAm.getCurrentUser();
-
-    /** Private constructor */
-    private SystemServicesProxy(Context context) {
-        mContext = context.getApplicationContext();
-        mAccm = AccessibilityManager.getInstance(context);
-        mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-        mIam = ActivityManager.getService();
-        mIatm = ActivityTaskManager.getService();
-        mPm = context.getPackageManager();
-        mIpm = AppGlobals.getPackageManager();
-        mAssistUtils = new AssistUtils(context);
-        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mIwm = WindowManagerGlobal.getWindowManagerService();
-        mUm = UserManager.get(context);
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.checkService(DreamService.DREAM_SERVICE));
-        mDisplay = mWm.getDefaultDisplay();
-        mRecentsPackage = context.getPackageName();
-        mIsSafeMode = mPm.isSafeMode();
-        mCurrentUserId = mAm.getCurrentUser();
-
-        // Get the dummy thumbnail width/heights
-        Resources res = context.getResources();
-        int wId = com.android.internal.R.dimen.thumbnail_width;
-        int hId = com.android.internal.R.dimen.thumbnail_height;
-        mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
-        mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
-
-        // Create the protection paints
-        mBgProtectionPaint = new Paint();
-        mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
-        mBgProtectionPaint.setColor(0xFFffffff);
-        mBgProtectionCanvas = new Canvas();
-
-        // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
-        // per-process listener to keep track of the current user id to reduce the number of binder
-        // calls to fetch it.
-        UserInfoController userInfoController = Dependency.get(UserInfoController.class);
-        userInfoController.addCallback(mOnUserInfoChangedListener);
-    }
-
-    /**
-     * Returns the single instance of the {@link SystemServicesProxy}.
-     * This should only be called on the main thread.
-     */
-    public static synchronized SystemServicesProxy getInstance(Context context) {
-        if (sSystemServicesProxy == null) {
-            sSystemServicesProxy = new SystemServicesProxy(context);
-        }
-        return sSystemServicesProxy;
-    }
-
-    /**
-     * Requests a gc() from the background thread.
-     */
-    public void gc() {
-        BackgroundThread.getHandler().post(mGcRunnable);
-    }
-
-    /**
-     * Returns whether the recents activity is currently visible.
-     */
-    public boolean isRecentsActivityVisible() {
-        return isRecentsActivityVisible(null);
-    }
-
-    /**
-     * Returns whether the recents activity is currently visible.
-     *
-     * @param isHomeStackVisible if provided, will return whether the home stack is visible
-     *                           regardless of the recents visibility
-     *
-     * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
-     */
-    public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
-        if (mIam == null) return false;
-
-        try {
-            List<StackInfo> stackInfos = mIatm.getAllStackInfos();
-            ActivityManager.StackInfo homeStackInfo = null;
-            ActivityManager.StackInfo fullscreenStackInfo = null;
-            ActivityManager.StackInfo recentsStackInfo = null;
-            for (int i = 0; i < stackInfos.size(); i++) {
-                final StackInfo stackInfo = stackInfos.get(i);
-                final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
-                final int activityType = winConfig.getActivityType();
-                final int windowingMode = winConfig.getWindowingMode();
-                if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
-                    homeStackInfo = stackInfo;
-                } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
-                        && (windowingMode == WINDOWING_MODE_FULLSCREEN
-                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
-                    fullscreenStackInfo = stackInfo;
-                } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
-                    recentsStackInfo = stackInfo;
-                }
-            }
-            boolean homeStackVisibleNotOccluded = isStackNotOccluded(homeStackInfo,
-                    fullscreenStackInfo);
-            boolean recentsStackVisibleNotOccluded = isStackNotOccluded(recentsStackInfo,
-                    fullscreenStackInfo);
-            if (isHomeStackVisible != null) {
-                isHomeStackVisible.value = homeStackVisibleNotOccluded;
-            }
-            ComponentName topActivity = recentsStackInfo != null ?
-                    recentsStackInfo.topActivity : null;
-            return (recentsStackVisibleNotOccluded && topActivity != null
-                    && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE)
-                    && LegacyRecentsImpl.RECENTS_ACTIVITIES.contains(topActivity.getClassName()));
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    private boolean isStackNotOccluded(ActivityManager.StackInfo stackInfo,
-            ActivityManager.StackInfo fullscreenStackInfo) {
-        boolean stackVisibleNotOccluded = stackInfo == null || stackInfo.visible;
-        if (fullscreenStackInfo != null && stackInfo != null) {
-            boolean isFullscreenStackOccludingg = fullscreenStackInfo.visible &&
-                    fullscreenStackInfo.position > stackInfo.position;
-            stackVisibleNotOccluded &= !isFullscreenStackOccludingg;
-        }
-        return stackVisibleNotOccluded;
-    }
-
-    /**
-     * Returns whether this device is in the safe mode.
-     */
-    public boolean isInSafeMode() {
-        return mIsSafeMode;
-    }
-
-    /** Moves an already resumed task to the side of the screen to initiate split screen. */
-    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
-            Rect initialBounds) {
-        if (mIatm == null) {
-            return false;
-        }
-
-        try {
-            return mIatm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode,
-                    true /* onTop */, false /* animate */, initialBounds, true /* showRecents */);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    public ActivityManager.StackInfo getSplitScreenPrimaryStack() {
-        try {
-            return mIatm.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
-     * @return whether there are any docked tasks for the current user.
-     */
-    public boolean hasDockedTask() {
-        if (mIam == null) return false;
-
-        ActivityManager.StackInfo stackInfo = getSplitScreenPrimaryStack();
-        if (stackInfo != null) {
-            int userId = getCurrentUser();
-            boolean hasUserTask = false;
-            for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) {
-                hasUserTask = (stackInfo.taskUserIds[i] == userId);
-            }
-            return hasUserTask;
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether there is a soft nav bar on specified display.
-     *
-     * @param displayId the id of display to check if there is a software navigation bar.
-     */
-    public boolean hasSoftNavigationBar(int displayId) {
-        try {
-            return mIwm.hasNavigationBar(displayId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether the device has a transposed nav bar (on the right of the screen) in the
-     * current display orientation.
-     */
-    public boolean hasTransposedNavigationBar() {
-        Rect insets = new Rect();
-        getStableInsets(insets);
-        return insets.right > 0;
-    }
-
-    /** Set the task's windowing mode. */
-    public void setTaskWindowingMode(int taskId, int windowingMode) {
-        if (mIatm == null) return;
-
-        try {
-            mIatm.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
-        } catch (RemoteException | IllegalArgumentException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Returns whether the provided {@param userId} represents the system user.
-     */
-    public boolean isSystemUser(int userId) {
-        return userId == UserHandle.USER_SYSTEM;
-    }
-
-    /**
-     * Returns the current user id.  Used instead of KeyguardUpdateMonitor in SystemUI components
-     * that run in the non-primary SystemUI process.
-     */
-    public int getCurrentUser() {
-        return mCurrentUserId;
-    }
-
-    /**
-     * Returns the processes user id.
-     */
-    public int getProcessUser() {
-        if (mUm == null) return 0;
-        return mUm.getUserHandle();
-    }
-
-    /**
-     * Returns whether touch exploration is currently enabled.
-     */
-    public boolean isTouchExplorationEnabled() {
-        if (mAccm == null) return false;
-
-        return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled();
-    }
-
-    /**
-     * Returns whether the current task is in screen-pinning mode.
-     */
-    public boolean isScreenPinningActive() {
-        if (mIam == null) return false;
-
-        try {
-            return mIatm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
-        } catch (RemoteException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Returns the smallest width/height.
-     */
-    public int getDeviceSmallestWidth() {
-        if (mDisplay == null) return 0;
-
-        Point smallestSizeRange = new Point();
-        Point largestSizeRange = new Point();
-        mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange);
-        return smallestSizeRange.x;
-    }
-
-    /**
-     * Returns the current display rect in the current display orientation.
-     */
-    public Rect getDisplayRect() {
-        Rect displayRect = new Rect();
-        if (mDisplay == null) return displayRect;
-
-        Point p = new Point();
-        mDisplay.getRealSize(p);
-        displayRect.set(0, 0, p.x, p.y);
-        return displayRect;
-    }
-
-    /**
-     * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack
-     */
-    public Rect getWindowRect() {
-        Rect windowRect = new Rect();
-        if (mIam == null) return windowRect;
-
-        try {
-            // Use the recents stack bounds, fallback to fullscreen stack if it is null
-            ActivityManager.StackInfo stackInfo =
-                    mIatm.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
-            if (stackInfo == null) {
-                stackInfo = mIatm.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-            }
-            if (stackInfo != null) {
-                windowRect.set(stackInfo.bounds);
-            }
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        } finally {
-            return windowRect;
-        }
-    }
-
-    public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) {
-        mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent,
-                opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
-    }
-
-    /** Starts an in-place animation on the front most application windows. */
-    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
-        if (mIam == null) return;
-
-        try {
-            mIatm.startInPlaceAnimationOnFrontMostApplication(
-                    opts == null ? null : opts.toBundle());
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    public void registerDockedStackListener(IDockedStackListener listener) {
-        if (mWm == null) return;
-
-        try {
-            mIwm.registerDockedStackListener(listener);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Calculates the size of the dock divider in the current orientation.
-     */
-    public int getDockedDividerSize(Context context) {
-        Resources res = context.getResources();
-        int dividerWindowWidth = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_thickness);
-        int dividerInsets = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_insets);
-        return dividerWindowWidth - 2 * dividerInsets;
-    }
-
-    public void requestKeyboardShortcuts(
-            Context context, KeyboardShortcutsReceiver receiver, int deviceId) {
-        mWm.requestAppKeyboardShortcuts(receiver, deviceId);
-    }
-
-    public void getStableInsets(Rect outStableInsets) {
-        if (mWm == null) return;
-
-        try {
-            mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Updates the visibility of recents.
-     */
-    public void setRecentsVisibility(final boolean visible) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIwm.setRecentsVisibility(visible);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to reach window manager", e);
-            }
-        });
-    }
-
-    /**
-     * Updates the visibility of the picture-in-picture.
-     */
-    public void setPipVisibility(final boolean visible) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIwm.setPipVisibility(visible);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to reach window manager", e);
-            }
-        });
-    }
-
-    public boolean isDreaming() {
-        try {
-            return mDreamManager.isDreaming();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to query dream manager.", e);
-        }
-        return false;
-    }
-
-    public void awakenDreamsAsync() {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mDreamManager.awaken();
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        });
-    }
-
-    public interface StartActivityFromRecentsResultListener {
-        void onStartActivityResult(boolean succeeded);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/BackgroundTaskLoader.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/BackgroundTaskLoader.java
deleted file mode 100644
index e85a7fb..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/BackgroundTaskLoader.java
+++ /dev/null
@@ -1,169 +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.recents.model;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-
-import com.android.systemui.shared.recents.model.IconLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-/**
- * Background task resource loader
- */
-class BackgroundTaskLoader implements Runnable {
-    static String TAG = "BackgroundTaskLoader";
-    static boolean DEBUG = false;
-
-    private Context mContext;
-    private final HandlerThread mLoadThread;
-    private final Handler mLoadThreadHandler;
-    private final Handler mMainThreadHandler;
-
-    private final TaskResourceLoadQueue mLoadQueue;
-    private final IconLoader mIconLoader;
-
-    private boolean mStarted;
-    private boolean mCancelled;
-    private boolean mWaitingOnLoadQueue;
-
-    private final OnIdleChangedListener mOnIdleChangedListener;
-
-    /** Constructor, creates a new loading thread that loads task resources in the background */
-    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
-            IconLoader iconLoader, OnIdleChangedListener onIdleChangedListener) {
-        mLoadQueue = loadQueue;
-        mIconLoader = iconLoader;
-        mMainThreadHandler = new Handler();
-        mOnIdleChangedListener = onIdleChangedListener;
-        mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
-                android.os.Process.THREAD_PRIORITY_BACKGROUND);
-        mLoadThread.start();
-        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
-    }
-
-    /** Restarts the loader thread */
-    void start(Context context) {
-        mContext = context;
-        mCancelled = false;
-        if (!mStarted) {
-            // Start loading on the load thread
-            mStarted = true;
-            mLoadThreadHandler.post(this);
-        } else {
-            // Notify the load thread to start loading again
-            synchronized (mLoadThread) {
-                mLoadThread.notifyAll();
-            }
-        }
-    }
-
-    /** Requests the loader thread to stop after the current iteration */
-    void stop() {
-        // Mark as cancelled for the thread to pick up
-        mCancelled = true;
-        // If we are waiting for the load queue for more tasks, then we can just reset the
-        // Context now, since nothing is using it
-        if (mWaitingOnLoadQueue) {
-            mContext = null;
-        }
-    }
-
-    @Override
-    public void run() {
-        while (true) {
-            if (mCancelled) {
-                // We have to unset the context here, since the background thread may be using it
-                // when we call stop()
-                mContext = null;
-                // If we are cancelled, then wait until we are started again
-                synchronized(mLoadThread) {
-                    try {
-                        mLoadThread.wait();
-                    } catch (InterruptedException ie) {
-                        ie.printStackTrace();
-                    }
-                }
-            } else {
-                // If we've stopped the loader, then fall through to the above logic to wait on
-                // the load thread
-                processLoadQueueItem();
-
-                // If there are no other items in the list, then just wait until something is added
-                if (!mCancelled && mLoadQueue.isEmpty()) {
-                    synchronized(mLoadQueue) {
-                        try {
-                            mWaitingOnLoadQueue = true;
-                            mMainThreadHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mOnIdleChangedListener.onIdleChanged(true);
-                                }
-                            });
-                            mLoadQueue.wait();
-                            mMainThreadHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mOnIdleChangedListener.onIdleChanged(false);
-                                }
-                            });
-                            mWaitingOnLoadQueue = false;
-                        } catch (InterruptedException ie) {
-                            ie.printStackTrace();
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * This needs to be in a separate method to work around an surprising interpreter behavior:
-     * The register will keep the local reference to cachedThumbnailData even if it falls out of
-     * scope. Putting it into a method fixes this issue.
-     */
-    private void processLoadQueueItem() {
-        // Load the next item from the queue
-        final Task t = mLoadQueue.nextTask();
-        if (t != null) {
-            final Drawable icon = mIconLoader.getIcon(t);
-            if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-            final ThumbnailData thumbnailData =
-                    ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id,
-                            true /* reducedResolution */);
-
-            if (!mCancelled) {
-                // Notify that the task data has changed
-                mMainThreadHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        t.notifyTaskDataLoaded(thumbnailData, icon);
-                    }
-                });
-            }
-        }
-    }
-
-    interface OnIdleChangedListener {
-        void onIdleChanged(boolean idle);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/FilteredTaskList.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/FilteredTaskList.java
deleted file mode 100644
index 005be75..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/FilteredTaskList.java
+++ /dev/null
@@ -1,125 +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.recents.model;
-
-import android.util.ArrayMap;
-import android.util.SparseArray;
-
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A list of filtered tasks.
- */
-class FilteredTaskList {
-
-    private final ArrayList<Task> mTasks = new ArrayList<>();
-    private final ArrayList<Task> mFilteredTasks = new ArrayList<>();
-    private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>();
-    private TaskFilter mFilter;
-
-    /** Sets the task filter, and returns whether the set of filtered tasks have changed. */
-    boolean setFilter(TaskFilter filter) {
-        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
-        mFilter = filter;
-        updateFilteredTasks();
-        return !prevFilteredTasks.equals(mFilteredTasks);
-    }
-
-    /** Adds a new task to the task list */
-    void add(Task t) {
-        mTasks.add(t);
-        updateFilteredTasks();
-    }
-
-    /** Sets the list of tasks */
-    void set(List<Task> tasks) {
-        mTasks.clear();
-        mTasks.addAll(tasks);
-        updateFilteredTasks();
-    }
-
-    /** Removes a task from the base list only if it is in the filtered list */
-    boolean remove(Task t) {
-        if (mFilteredTasks.contains(t)) {
-            boolean removed = mTasks.remove(t);
-            updateFilteredTasks();
-            return removed;
-        }
-        return false;
-    }
-
-    /** Returns the index of this task in the list of filtered tasks */
-    int indexOf(Task t) {
-        if (t != null && mFilteredTaskIndices.containsKey(t.key)) {
-            return mFilteredTaskIndices.get(t.key);
-        }
-        return -1;
-    }
-
-    /** Returns the size of the list of filtered tasks */
-    int size() {
-        return mFilteredTasks.size();
-    }
-
-    /** Returns whether the filtered list contains this task */
-    boolean contains(Task t) {
-        return mFilteredTaskIndices.containsKey(t.key);
-    }
-
-    /** Updates the list of filtered tasks whenever the base task list changes */
-    private void updateFilteredTasks() {
-        mFilteredTasks.clear();
-        if (mFilter != null) {
-            // Create a sparse array from task id to Task
-            SparseArray<Task> taskIdMap = new SparseArray<>();
-            int taskCount = mTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                taskIdMap.put(t.key.id, t);
-            }
-
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                if (mFilter.acceptTask(taskIdMap, t, i)) {
-                    mFilteredTasks.add(t);
-                }
-            }
-        } else {
-            mFilteredTasks.addAll(mTasks);
-        }
-        updateFilteredTaskIndices();
-    }
-
-    /** Updates the mapping of tasks to indices. */
-    private void updateFilteredTaskIndices() {
-        int taskCount = mFilteredTasks.size();
-        mFilteredTaskIndices.clear();
-        for (int i = 0; i < taskCount; i++) {
-            Task t = mFilteredTasks.get(i);
-            mFilteredTaskIndices.put(t.key, i);
-        }
-    }
-
-    /** Returns the list of filtered tasks */
-    ArrayList<Task> getTasks() {
-        return mFilteredTasks;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
deleted file mode 100644
index 34bc334..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ /dev/null
@@ -1,247 +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.recents.model;
-
-import static android.os.Process.setThreadPriority;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-/**
- * Loader class that loads full-resolution thumbnails when appropriate.
- */
-public class HighResThumbnailLoader implements
-        TaskCallbacks, BackgroundTaskLoader.OnIdleChangedListener {
-
-    private final ActivityManagerWrapper mActivityManager;
-
-    @GuardedBy("mLoadQueue")
-    private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
-    @GuardedBy("mLoadQueue")
-    private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
-    @GuardedBy("mLoadQueue")
-    private boolean mLoaderIdling;
-
-    private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
-
-    private final Thread mLoadThread;
-    private final Handler mMainThreadHandler;
-    private final boolean mIsLowRamDevice;
-    private boolean mLoading;
-    private boolean mVisible;
-    private boolean mFlingingFast;
-    private boolean mTaskLoadQueueIdle;
-
-    public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper,
-            boolean isLowRamDevice) {
-        mActivityManager = activityManager;
-        mMainThreadHandler = new Handler(looper);
-        mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
-        mLoadThread.start();
-        mIsLowRamDevice = isLowRamDevice;
-    }
-
-    public void setVisible(boolean visible) {
-        if (mIsLowRamDevice) {
-            return;
-        }
-        mVisible = visible;
-        updateLoading();
-    }
-
-    public void setFlingingFast(boolean flingingFast) {
-        if (mFlingingFast == flingingFast || mIsLowRamDevice) {
-            return;
-        }
-        mFlingingFast = flingingFast;
-        updateLoading();
-    }
-
-    @Override
-    public void onIdleChanged(boolean idle) {
-        setTaskLoadQueueIdle(idle);
-    }
-
-    /**
-     * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not
-     * starting this queue until the other queue is idling.
-     */
-    public void setTaskLoadQueueIdle(boolean idle) {
-        if (mIsLowRamDevice) {
-            return;
-        }
-        mTaskLoadQueueIdle = idle;
-        updateLoading();
-    }
-
-    @VisibleForTesting
-    boolean isLoading() {
-        return mLoading;
-    }
-
-    private void updateLoading() {
-        setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle);
-    }
-
-    private void setLoading(boolean loading) {
-        if (loading == mLoading) {
-            return;
-        }
-        synchronized (mLoadQueue) {
-            mLoading = loading;
-            if (!loading) {
-                stopLoading();
-            } else {
-                startLoading();
-            }
-        }
-    }
-
-    @GuardedBy("mLoadQueue")
-    private void startLoading() {
-        for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
-            Task t = mVisibleTasks.get(i);
-            if ((t.thumbnail == null || t.thumbnail.reducedResolution)
-                    && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
-                mLoadQueue.add(t);
-            }
-        }
-        mLoadQueue.notifyAll();
-    }
-
-    @GuardedBy("mLoadQueue")
-    private void stopLoading() {
-        mLoadQueue.clear();
-        mLoadQueue.notifyAll();
-    }
-
-    /**
-     * Needs to be called when a task becomes visible. Note that this is different from
-     * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
-     * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
-     * has been updated.
-     */
-    public void onTaskVisible(Task t) {
-        t.addCallback(this);
-        mVisibleTasks.add(t);
-        if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
-            synchronized (mLoadQueue) {
-                mLoadQueue.add(t);
-                mLoadQueue.notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
-     * different from {@link TaskCallbacks#onTaskDataUnloaded()}
-     */
-    public void onTaskInvisible(Task t) {
-        t.removeCallback(this);
-        mVisibleTasks.remove(t);
-        synchronized (mLoadQueue) {
-            mLoadQueue.remove(t);
-        }
-    }
-
-    @VisibleForTesting
-    void waitForLoaderIdle() {
-        while (true) {
-            synchronized (mLoadQueue) {
-                if (mLoadQueue.isEmpty() && mLoaderIdling) {
-                    return;
-                }
-            }
-            SystemClock.sleep(100);
-        }
-    }
-
-    @Override
-    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
-        if (thumbnailData != null && !thumbnailData.reducedResolution) {
-            synchronized (mLoadQueue) {
-                mLoadQueue.remove(task);
-            }
-        }
-    }
-
-    @Override
-    public void onTaskDataUnloaded() {
-    }
-
-    @Override
-    public void onTaskWindowingModeChanged() {
-    }
-
-    private final Runnable mLoader = new Runnable() {
-
-        @Override
-        public void run() {
-            setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
-            while (true) {
-                Task next = null;
-                synchronized (mLoadQueue) {
-                    if (!mLoading || mLoadQueue.isEmpty()) {
-                        try {
-                            mLoaderIdling = true;
-                            mLoadQueue.wait();
-                            mLoaderIdling = false;
-                        } catch (InterruptedException e) {
-                            // Don't care.
-                        }
-                    } else {
-                        next = mLoadQueue.poll();
-                        if (next != null) {
-                            mLoadingTasks.add(next);
-                        }
-                    }
-                }
-                if (next != null) {
-                    loadTask(next);
-                }
-            }
-        }
-
-        private void loadTask(final Task t) {
-            final ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id,
-                    false /* reducedResolution */);
-            mMainThreadHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (mLoadQueue) {
-                        mLoadingTasks.remove(t);
-                    }
-                    if (mVisibleTasks.contains(t)) {
-                        t.notifyTaskDataLoaded(thumbnail, t.icon);
-                    }
-                }
-            });
-        }
-    };
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
deleted file mode 100644
index 2df79d8..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.KeyguardManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.util.SparseBooleanArray;
-
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class stores the loading state as it goes through multiple stages of loading:
- *   1) preloadRawTasks() will load the raw set of recents tasks from the system
- *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
- *      thumbnails that are currently in the cache
- *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
- *      options specified, such that we can transition into the Recents activity seamlessly
- */
-public class RecentsTaskLoadPlan {
-
-    /** The set of conditions to preload tasks. */
-    public static class PreloadOptions {
-        public boolean loadTitles = true;
-    }
-
-    /** The set of conditions to load tasks. */
-    public static class Options {
-        public int runningTaskId = -1;
-        public boolean loadIcons = true;
-        public boolean loadThumbnails = false;
-        public boolean onlyLoadForCache = false;
-        public boolean onlyLoadPausedActivities = false;
-        public int numVisibleTasks = 0;
-        public int numVisibleTaskThumbnails = 0;
-    }
-
-    private final Context mContext;
-    private final KeyguardManager mKeyguardManager;
-
-    private List<ActivityManager.RecentTaskInfo> mRawTasks;
-    private TaskStack mStack;
-
-    private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray();
-
-    public RecentsTaskLoadPlan(Context context) {
-        mContext = context;
-        mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
-    }
-
-    /**
-     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
-     * have a list of all the recent tasks with their metadata, not including icons or
-     * thumbnails which were not cached and have to be loaded.
-     *
-     * The tasks will be ordered by:
-     * - least-recent to most-recent stack tasks
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
-            int currentUserId) {
-        Resources res = mContext.getResources();
-        ArrayList<Task> allTasks = new ArrayList<>();
-        if (mRawTasks == null) {
-            mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
-                    ActivityTaskManager.getMaxRecentTasksStatic(), currentUserId);
-
-            // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
-            Collections.reverse(mRawTasks);
-        }
-
-        int taskCount = mRawTasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-
-            // Compose the task key
-            final ComponentName sourceComponent = t.origActivity != null
-                    // Activity alias if there is one
-                    ? t.origActivity
-                    // The real activity if there is no alias (or the target if there is one)
-                    : t.realActivity;
-            final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
-            TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
-                    sourceComponent, t.userId, t.lastActiveTime, t.displayId);
-
-            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
-            boolean isStackTask = !isFreeformTask;
-            boolean isLaunchTarget = taskKey.id == runningTaskId;
-
-            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
-            if (info == null) {
-                continue;
-            }
-
-            // Load the title, icon, and color
-            String title = opts.loadTitles
-                    ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
-                    : "";
-            String titleDescription = opts.loadTitles
-                    ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
-                    : "";
-            Drawable icon = isStackTask
-                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, false)
-                    : null;
-            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                    false /* loadIfNotCached */, false /* storeInCache */);
-            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
-            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
-            boolean isSystemApp = (info != null) &&
-                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
-
-            // TODO: Refactor to not do this every preload
-            if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
-                mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
-            }
-            boolean isLocked = mTmpLockedUsers.get(t.userId);
-
-            // Add the task to the stack
-            Task task = new Task(taskKey, icon,
-                    thumbnail, title, titleDescription, activityColor, backgroundColor,
-                    isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
-                    t.taskDescription, t.resizeMode, t.topActivity, isLocked);
-
-            allTasks.add(task);
-        }
-
-        // Initialize the stacks
-        mStack = new TaskStack();
-        mStack.setTasks(allTasks, false /* notifyStackChanges */);
-    }
-
-    /**
-     * Called to apply the actual loading based on the specified conditions.
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    public void executePlan(Options opts, RecentsTaskLoader loader) {
-        Resources res = mContext.getResources();
-
-        // Iterate through each of the tasks and load them according to the load conditions.
-        ArrayList<Task> tasks = mStack.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            TaskKey taskKey = task.key;
-
-            boolean isRunningTask = (task.key.id == opts.runningTaskId);
-            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
-            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
-            // If requested, skip the running task
-            if (opts.onlyLoadPausedActivities && isRunningTask) {
-                continue;
-            }
-
-            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
-                if (task.icon == null) {
-                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription,
-                            true);
-                }
-            }
-            if (opts.loadThumbnails && isVisibleThumbnail) {
-                task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                        true /* loadIfNotCached */, true /* storeInCache */);
-            }
-        }
-    }
-
-    /**
-     * Returns the TaskStack from the preloaded list of recent tasks.
-     */
-    public TaskStack getTaskStack() {
-        return mStack;
-    }
-
-    /** Returns whether there are any tasks in any stacks. */
-    public boolean hasTasks() {
-        if (mStack != null) {
-            return mStack.getTaskCount() > 0;
-        }
-        return false;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoader.java
deleted file mode 100644
index 012913a..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.Trace;
-import android.util.Log;
-import android.util.LruCache;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan.Options;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan.PreloadOptions;
-import com.android.systemui.shared.recents.model.IconLoader;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.io.PrintWriter;
-import java.util.Map;
-
-
-/**
- * Recents task loader
- */
-public class RecentsTaskLoader {
-    private static final String TAG = "RecentsTaskLoader";
-    private static final boolean DEBUG = false;
-
-    /** Levels of svelte in increasing severity/austerity. */
-    // No svelting.
-    public static final int SVELTE_NONE = 0;
-    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
-    // caching thumbnails as you scroll.
-    public static final int SVELTE_LIMIT_CACHE = 1;
-    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
-    // evict all thumbnails when hidden.
-    public static final int SVELTE_DISABLE_CACHE = 2;
-    // Disable all thumbnail loading.
-    public static final int SVELTE_DISABLE_LOADING = 3;
-
-    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
-    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
-    // below, this is per-package so we can't invalidate the items in the cache based on the last
-    // active time.  Instead, we rely on the PackageMonitor to keep us informed whenever a
-    // package in the cache has been updated, so that we may remove it.
-    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
-    private final TaskKeyLruCache<Drawable> mIconCache;
-    private final TaskKeyLruCache<String> mActivityLabelCache;
-    private final TaskKeyLruCache<String> mContentDescriptionCache;
-    private final TaskResourceLoadQueue mLoadQueue;
-    private final IconLoader mIconLoader;
-    private final BackgroundTaskLoader mLoader;
-    private final HighResThumbnailLoader mHighResThumbnailLoader;
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
-    private final int mMaxThumbnailCacheSize;
-    private final int mMaxIconCacheSize;
-    private int mNumVisibleTasksLoaded;
-    private int mSvelteLevel;
-
-    private int mDefaultTaskBarBackgroundColor;
-    private int mDefaultTaskViewBackgroundColor;
-
-    private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() {
-        @Override
-        public void onEntryEvicted(TaskKey key) {
-            if (key != null) {
-                mActivityInfoCache.remove(key.getComponent());
-            }
-        }
-    };
-
-    public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize,
-            int svelteLevel) {
-        mMaxThumbnailCacheSize = maxThumbnailCacheSize;
-        mMaxIconCacheSize = maxIconCacheSize;
-        mSvelteLevel = svelteLevel;
-
-        // Initialize the proxy, cache and loaders
-        int numRecentTasks = ActivityTaskManager.getMaxRecentTasksStatic();
-        mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(),
-                Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic());
-        mLoadQueue = new TaskResourceLoadQueue();
-        mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction);
-        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
-        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
-                mClearActivityInfoOnEviction);
-        mActivityInfoCache = new LruCache<>(numRecentTasks);
-
-        mIconLoader = createNewIconLoader(context, mIconCache, mActivityInfoCache);
-        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconLoader, mHighResThumbnailLoader);
-    }
-
-    protected IconLoader createNewIconLoader(Context context,TaskKeyLruCache<Drawable> iconCache,
-            LruCache<ComponentName, ActivityInfo> activityInfoCache) {
-        return new IconLoader.DefaultIconLoader(context, iconCache, activityInfoCache);
-    }
-
-    /**
-     * Sets the default task bar/view colors if none are provided by the app.
-     */
-    public void setDefaultColors(int defaultTaskBarBackgroundColor,
-            int defaultTaskViewBackgroundColor) {
-        mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor;
-        mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor;
-    }
-
-    /** Returns the size of the app icon cache. */
-    public int getIconCacheSize() {
-        return mMaxIconCacheSize;
-    }
-
-    /** Returns the size of the thumbnail cache. */
-    public int getThumbnailCacheSize() {
-        return mMaxThumbnailCacheSize;
-    }
-
-    public HighResThumbnailLoader getHighResThumbnailLoader() {
-        return mHighResThumbnailLoader;
-    }
-
-    /** Preloads recents tasks using the specified plan to store the output. */
-    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
-        preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
-    }
-
-    /** Preloads recents tasks using the specified plan to store the output. */
-    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
-            int currentUserId) {
-        try {
-            Trace.beginSection("preloadPlan");
-            plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /** Begins loading the heavy task data according to the specified options. */
-    public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) {
-        if (opts == null) {
-            throw new RuntimeException("Requires load options");
-        }
-        if (opts.onlyLoadForCache && opts.loadThumbnails) {
-            // If we are loading for the cache, we'd like to have the real cache only include the
-            // visible thumbnails. However, we also don't want to reload already cached thumbnails.
-            // Thus, we copy over the current entries into a second cache, and clear the real cache,
-            // such that the real cache only contains visible thumbnails.
-            mTempCache.copyEntries(mThumbnailCache);
-            mThumbnailCache.evictAll();
-        }
-        plan.executePlan(opts, this);
-        mTempCache.evictAll();
-        if (!opts.onlyLoadForCache) {
-            mNumVisibleTasksLoaded = opts.numVisibleTasks;
-        }
-    }
-
-    /**
-     * Acquires the task resource data directly from the cache, loading if necessary.
-     */
-    public void loadTaskData(Task t) {
-        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
-        icon = icon != null ? icon : mIconLoader.getDefaultIcon(t.key.userId);
-        mLoadQueue.addTask(t);
-        t.notifyTaskDataLoaded(t.thumbnail, icon);
-    }
-
-    /** Releases the task resource data back into the pool. */
-    public void unloadTaskData(Task t) {
-        mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId));
-    }
-
-    /** Completely removes the resource data from the pool. */
-    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
-        mLoadQueue.removeTask(t);
-        mIconCache.remove(t.key);
-        mActivityLabelCache.remove(t.key);
-        mContentDescriptionCache.remove(t.key);
-        if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId));
-        }
-    }
-
-    /**
-     * Handles signals from the system, trimming memory when requested to prevent us from running
-     * out of memory.
-     */
-    public synchronized void onTrimMemory(int level) {
-        switch (level) {
-            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
-                // Stop the loader immediately when the UI is no longer visible
-                stopLoader();
-                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
-                        mMaxIconCacheSize / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
-                // We are leaving recents, so trim the data a bit
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityTaskManager.getMaxRecentTasksStatic() / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                // We are going to be low on memory
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityTaskManager.getMaxRecentTasksStatic() / 4));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                // We are low on memory, so release everything
-                mIconCache.evictAll();
-                mActivityInfoCache.evictAll();
-                // The cache is small, only clear the label cache when we are critical
-                mActivityLabelCache.evictAll();
-                mContentDescriptionCache.evictAll();
-                mThumbnailCache.evictAll();
-                break;
-            default:
-                break;
-        }
-    }
-
-    public void onPackageChanged(String packageName) {
-        // Remove all the cached activity infos for this package.  The other caches do not need to
-        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
-        // cached contents are requested
-        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
-        for (ComponentName cn : activityInfoCache.keySet()) {
-            if (cn.getPackageName().equals(packageName)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Removing activity info from cache: " + cn);
-                }
-                mActivityInfoCache.remove(cn);
-            }
-        }
-    }
-
-    /**
-     * Returns the cached task label if the task key is not expired, updating the cache if it is.
-     */
-    String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) {
-        // Return the task description label if it exists
-        if (td != null && td.getLabel() != null) {
-            return td.getLabel();
-        }
-        // Return the cached activity label if it exists
-        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo,
-                    taskKey.userId);
-            mActivityLabelCache.put(taskKey, label);
-            return label;
-        }
-        // If the activity info does not exist or fails to load, return an empty label for now,
-        // but do not cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task content description if the task key is not expired, updating the
-     * cache if it is.
-     */
-    String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) {
-        // Return the cached content description if it exists
-        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(
-                    activityInfo, taskKey.userId, td);
-            if (td == null) {
-                // Only add to the cache if the task description is null, otherwise, it is possible
-                // for the task description to change between calls without the last active time
-                // changing (ie. between preloading and Overview starting) which would lead to stale
-                // content descriptions
-                // TODO: Investigate improving this
-                mContentDescriptionCache.put(taskKey, label);
-            }
-            return label;
-        }
-        // If the content description does not exist, return an empty label for now, but do not
-        // cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
-     */
-    Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td,
-            boolean loadIfNotCached) {
-        return mIconLoader.getAndInvalidateIfModified(taskKey, td, loadIfNotCached);
-    }
-
-    /**
-     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
-     */
-    synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached,
-            boolean storeInCache) {
-        ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            return cached;
-        }
-
-        cached = mTempCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            mThumbnailCache.put(taskKey, cached);
-            return cached;
-        }
-
-        if (loadIfNotCached) {
-            if (mSvelteLevel < SVELTE_DISABLE_LOADING) {
-                // Load the thumbnail from the system
-                ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
-                        taskKey.id, true /* reducedResolution */);
-                if (thumbnailData.thumbnail != null) {
-                    if (storeInCache) {
-                        mThumbnailCache.put(taskKey, thumbnailData);
-                    }
-                    return thumbnailData;
-                }
-            }
-        }
-
-        // We couldn't load any thumbnail
-        return null;
-    }
-
-    /**
-     * Returns the task's primary color if possible, defaulting to the default color if there is
-     * no specified primary color.
-     */
-    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getPrimaryColor() != 0) {
-            return td.getPrimaryColor();
-        }
-        return mDefaultTaskBarBackgroundColor;
-    }
-
-    /**
-     * Returns the task's background color if possible.
-     */
-    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getBackgroundColor() != 0) {
-            return td.getBackgroundColor();
-        }
-        return mDefaultTaskViewBackgroundColor;
-    }
-
-    /**
-     * Returns the activity info for the given task key, retrieving one from the system if the
-     * task key is expired.
-     */
-    ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
-        return mIconLoader.getAndUpdateActivityInfo(taskKey);
-    }
-
-    /**
-     * Starts loading tasks.
-     */
-    public void startLoader(Context ctx) {
-        mLoader.start(ctx);
-    }
-
-    /**
-     * Stops the task loader and clears all queued, pending task loads.
-     */
-    private void stopLoader() {
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
-    public synchronized void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.println(TAG);
-        writer.print(prefix); writer.println("Icon Cache");
-        mIconCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Thumbnail Cache");
-        mThumbnailCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Temp Thumbnail Cache");
-        mTempCache.dump(innerPrefix, writer);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskFilter.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskFilter.java
deleted file mode 100644
index 9b734ec..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskFilter.java
+++ /dev/null
@@ -1,28 +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.recents.model;
-
-import android.util.SparseArray;
-import com.android.systemui.shared.recents.model.Task;
-
-/**
- * An interface for a task filter to query whether a particular task should show in a stack.
- */
-public interface TaskFilter {
-    /** Returns whether the filter accepts the specified task */
-    boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
deleted file mode 100644
index 27f2098..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
+++ /dev/null
@@ -1,73 +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.recents.model;
-
-import android.util.ArrayMap;
-
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-
-import com.android.systemui.shared.recents.model.TaskKeyCache;
-import com.android.systemui.shared.recents.model.TaskKeyLruCache;
-import java.io.PrintWriter;
-
-/**
- * Like {@link TaskKeyLruCache}, but without LRU functionality.
- */
-public class TaskKeyStrongCache<V> extends TaskKeyCache<V> {
-
-    private static final String TAG = "TaskKeyCache";
-
-    private final ArrayMap<Integer, V> mCache = new ArrayMap<>();
-
-    public final void copyEntries(TaskKeyStrongCache<V> other) {
-        for (int i = other.mKeys.size() - 1; i >= 0; i--) {
-            TaskKey key = other.mKeys.valueAt(i);
-            put(key, other.mCache.get(key.id));
-        }
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numEntries="); writer.print(mKeys.size());
-        writer.println();
-        int keyCount = mKeys.size();
-        for (int i = 0; i < keyCount; i++) {
-            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
-        }
-    }
-
-    @Override
-    protected V getCacheEntry(int id) {
-        return mCache.get(id);
-    }
-
-    @Override
-    protected void putCacheEntry(int id, V value) {
-        mCache.put(id, value);
-    }
-
-    @Override
-    protected void removeCacheEntry(int id) {
-        mCache.remove(id);
-    }
-
-    @Override
-    protected void evictAllCache() {
-        mCache.clear();
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskResourceLoadQueue.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskResourceLoadQueue.java
deleted file mode 100644
index fe89ad5..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskResourceLoadQueue.java
+++ /dev/null
@@ -1,61 +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.recents.model;
-
-import com.android.systemui.shared.recents.model.Task;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * A Task load queue
- */
-class TaskResourceLoadQueue {
-
-    private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>();
-
-    /** Adds a new task to the load queue */
-    void addTask(Task t) {
-        if (!mQueue.contains(t)) {
-            mQueue.add(t);
-        }
-        synchronized(this) {
-            notifyAll();
-        }
-    }
-
-    /**
-     * Retrieves the next task from the load queue, as well as whether we want that task to be
-     * force reloaded.
-     */
-    Task nextTask() {
-        return mQueue.poll();
-    }
-
-    /** Removes a task from the load queue */
-    void removeTask(Task t) {
-        mQueue.remove(t);
-    }
-
-    /** Clears all the tasks from the load queue */
-    void clearTasks() {
-        mQueue.clear();
-    }
-
-    /** Returns whether the load queue is empty */
-    boolean isEmpty() {
-        return mQueue.isEmpty();
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskStack.java
deleted file mode 100644
index 01e6ba3..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/model/TaskStack.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.content.ComponentName;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * The task stack contains a list of multiple tasks.
- */
-public class TaskStack {
-
-    private static final String TAG = "TaskStack";
-
-    /** Task stack callbacks */
-    public interface TaskStackCallbacks {
-        /**
-         * Notifies when a new task has been added to the stack.
-         */
-        void onStackTaskAdded(TaskStack stack, Task newTask);
-
-        /**
-         * Notifies when a task has been removed from the stack.
-         */
-        void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
-                AnimationProps animation, boolean fromDockGesture,
-                boolean dismissRecentsIfAllRemoved);
-
-        /**
-         * Notifies when all tasks have been removed from the stack.
-         */
-        void onStackTasksRemoved(TaskStack stack);
-
-        /**
-         * Notifies when tasks in the stack have been updated.
-         */
-        void onStackTasksUpdated(TaskStack stack);
-    }
-
-    private final ArrayList<Task> mRawTaskList = new ArrayList<>();
-    private final FilteredTaskList mStackTaskList = new FilteredTaskList();
-    private TaskStackCallbacks mCb;
-
-    public TaskStack() {
-        // Ensure that we only show stack tasks
-        mStackTaskList.setFilter(new TaskFilter() {
-            @Override
-            public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
-                return  t.isStackTask;
-            }
-        });
-    }
-
-    /** Sets the callbacks for this task stack. */
-    public void setCallbacks(TaskStackCallbacks cb) {
-        mCb = cb;
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
-        removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
-            boolean dismissRecentsIfAllRemoved) {
-        if (mStackTaskList.contains(t)) {
-            mStackTaskList.remove(t);
-            Task newFrontMostTask = getFrontMostTask();
-            if (mCb != null) {
-                // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
-                        fromDockGesture, dismissRecentsIfAllRemoved);
-            }
-        }
-        mRawTaskList.remove(t);
-    }
-
-    /**
-     * Removes all tasks from the stack.
-     */
-    public void removeAllTasks(boolean notifyStackChanges) {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task t = tasks.get(i);
-            mStackTaskList.remove(t);
-            mRawTaskList.remove(t);
-        }
-        if (mCb != null && notifyStackChanges) {
-            // Notify that all tasks have been removed
-            mCb.onStackTasksRemoved(this);
-        }
-    }
-
-
-    /**
-     * @see #setTasks(List, boolean)
-     */
-    public void setTasks(TaskStack stack, boolean notifyStackChanges) {
-        setTasks(stack.mRawTaskList, notifyStackChanges);
-    }
-
-    /**
-     * Sets a few tasks in one go, without calling any callbacks.
-     *
-     * @param tasks the new set of tasks to replace the current set.
-     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
-     */
-    public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
-        // Compute a has set for each of the tasks
-        ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
-        ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
-        ArrayList<Task> addedTasks = new ArrayList<>();
-        ArrayList<Task> removedTasks = new ArrayList<>();
-        ArrayList<Task> allTasks = new ArrayList<>();
-
-        // Disable notifications if there are no callbacks
-        if (mCb == null) {
-            notifyStackChanges = false;
-        }
-
-        // Remove any tasks that no longer exist
-        int taskCount = mRawTaskList.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = mRawTaskList.get(i);
-            if (!newTasksMap.containsKey(task.key)) {
-                if (notifyStackChanges) {
-                    removedTasks.add(task);
-                }
-            }
-        }
-
-        // Add any new tasks
-        taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task newTask = tasks.get(i);
-            Task currentTask = currentTasksMap.get(newTask.key);
-            if (currentTask == null && notifyStackChanges) {
-                addedTasks.add(newTask);
-            } else if (currentTask != null) {
-                // The current task has bound callbacks, so just copy the data from the new task
-                // state and add it back into the list
-                currentTask.copyFrom(newTask);
-                newTask = currentTask;
-            }
-            allTasks.add(newTask);
-        }
-
-        // Sort all the tasks to ensure they are ordered correctly
-        for (int i = allTasks.size() - 1; i >= 0; i--) {
-            allTasks.get(i).temporarySortIndexInStack = i;
-        }
-
-        mStackTaskList.set(allTasks);
-        mRawTaskList.clear();
-        mRawTaskList.addAll(allTasks);
-
-        // Only callback for the removed tasks after the stack has updated
-        int removedTaskCount = removedTasks.size();
-        Task newFrontMostTask = getFrontMostTask();
-        for (int i = 0; i < removedTaskCount; i++) {
-            mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
-                    AnimationProps.IMMEDIATE, false /* fromDockGesture */,
-                    true /* dismissRecentsIfAllRemoved */);
-        }
-
-        // Only callback for the newly added tasks after this stack has been updated
-        int addedTaskCount = addedTasks.size();
-        for (int i = 0; i < addedTaskCount; i++) {
-            mCb.onStackTaskAdded(this, addedTasks.get(i));
-        }
-
-        // Notify that the task stack has been updated
-        if (notifyStackChanges) {
-            mCb.onStackTasksUpdated(this);
-        }
-    }
-
-    /**
-     * Gets the front-most task in the stack.
-     */
-    public Task getFrontMostTask() {
-        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
-        if (stackTasks.isEmpty()) {
-            return null;
-        }
-        return stackTasks.get(stackTasks.size() - 1);
-    }
-
-    /** Gets the task keys */
-    public ArrayList<TaskKey> getTaskKeys() {
-        ArrayList<TaskKey> taskKeys = new ArrayList<>();
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            taskKeys.add(task.key);
-        }
-        return taskKeys;
-    }
-
-    /**
-     * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
-     */
-    public ArrayList<Task> getTasks() {
-        return mStackTaskList.getTasks();
-    }
-
-    /**
-     * Computes a set of all the active and historical tasks.
-     */
-    public ArrayList<Task> computeAllTasksList() {
-        ArrayList<Task> tasks = new ArrayList<>();
-        tasks.addAll(mStackTaskList.getTasks());
-        return tasks;
-    }
-
-    /**
-     * Returns the number of stack tasks.
-     */
-    public int getTaskCount() {
-        return mStackTaskList.size();
-    }
-
-    /**
-     * Returns the task in stack tasks which is the launch target.
-     */
-    public Task getLaunchTarget() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isLaunchTarget) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns whether the next launch target should actually be the PiP task.
-     */
-    public boolean isNextLaunchTargetPip(long lastPipTime) {
-        Task launchTarget = getLaunchTarget();
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null && lastPipTime > 0) {
-            // If the PiP time is more recent than the next launch target, then launch the PiP task
-            return lastPipTime > nextLaunchTarget.key.lastActiveTime;
-        } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
-            // Otherwise, if there is no next launch target, but there is a PiP, then launch
-            // the PiP task
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns the task in stack tasks which should be launched next if Recents are toggled
-     * again, or null if there is no task to be launched. Callers should check
-     * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
-     * stack.
-     */
-    public Task getNextLaunchTarget() {
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null) {
-            return nextLaunchTarget;
-        }
-        return getTasks().get(getTaskCount() - 1);
-    }
-
-    private Task getNextLaunchTargetRaw() {
-        int taskCount = getTaskCount();
-        if (taskCount == 0) {
-            return null;
-        }
-        int launchTaskIndex = indexOfTask(getLaunchTarget());
-        if (launchTaskIndex != -1 && launchTaskIndex > 0) {
-            return getTasks().get(launchTaskIndex - 1);
-        }
-        return null;
-    }
-
-    /** Returns the index of this task in this current task stack */
-    public int indexOfTask(Task t) {
-        return mStackTaskList.indexOf(t);
-    }
-
-    /** Finds the task with the specified task id. */
-    public Task findTaskWithId(int taskId) {
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == taskId) {
-                return task;
-            }
-        }
-        return null;
-    }
-    
-    /**
-     * Computes the components of tasks in this stack that have been removed as a result of a change
-     * in the specified package.
-     */
-    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        ArraySet<ComponentName> existingComponents = new ArraySet<>();
-        ArraySet<ComponentName> removedComponents = new ArraySet<>();
-        ArrayList<TaskKey> taskKeys = getTaskKeys();
-        int taskKeyCount = taskKeys.size();
-        for (int i = 0; i < taskKeyCount; i++) {
-            TaskKey t = taskKeys.get(i);
-
-            // Skip if this doesn't apply to the current user
-            if (t.userId != userId) continue;
-
-            ComponentName cn = t.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (existingComponents.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
-                    existingComponents.add(cn);
-                } else {
-                    removedComponents.add(cn);
-                }
-            }
-        }
-        return removedComponents;
-    }
-
-    @Override
-    public String toString() {
-        String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            str += "    " + tasks.get(i).toString() + "\n";
-        }
-        return str;
-    }
-
-    /**
-     * Given a list of tasks, returns a map of each task's key to the task.
-     */
-    private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
-        ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            map.put(task.key, task);
-        }
-        return map;
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
-        writer.println();
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            tasks.get(i).dump(innerPrefix, writer);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/AnimationProps.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/AnimationProps.java
deleted file mode 100644
index 5463998..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/AnimationProps.java
+++ /dev/null
@@ -1,229 +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.recents.utilities;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.util.SparseArray;
-import android.util.SparseLongArray;
-import android.view.View;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/**
- * The generic set of animation properties to animate a {@link View}. The animation can have
- * different interpolators, start delays and durations for each of the different properties.
- */
-public class AnimationProps {
-
-    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
-    public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR);
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
-    public @interface PropType {}
-
-    public static final int ALL = 0;
-    public static final int TRANSLATION_X = 1;
-    public static final int TRANSLATION_Y = 2;
-    public static final int TRANSLATION_Z = 3;
-    public static final int ALPHA = 4;
-    public static final int SCALE = 5;
-    public static final int BOUNDS = 6;
-    public static final int DIM_ALPHA = 7;
-
-    private SparseLongArray mPropStartDelay;
-    private SparseLongArray mPropDuration;
-    private SparseArray<Interpolator> mPropInterpolators;
-    private Animator.AnimatorListener mListener;
-
-    /**
-     * The builder constructor.
-     */
-    public AnimationProps() {}
-
-    /**
-     * Creates an animation with a default {@param duration} and {@param interpolator} for all
-     * properties in this animation.
-     */
-    public AnimationProps(int duration, Interpolator interpolator) {
-        this(0, duration, interpolator, null);
-    }
-
-    /**
-     * Creates an animation with a default {@param duration} and {@param interpolator} for all
-     * properties in this animation.
-     */
-    public AnimationProps(int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        this(0, duration, interpolator, listener);
-    }
-
-    /**
-     * Creates an animation with a default {@param startDelay}, {@param duration} and
-     * {@param interpolator} for all properties in this animation.
-     */
-    public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
-        this(startDelay, duration, interpolator, null);
-    }
-
-    /**
-     * Creates an animation with a default {@param startDelay}, {@param duration} and
-     * {@param interpolator} for all properties in this animation.
-     */
-    public AnimationProps(int startDelay, int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        setStartDelay(ALL, startDelay);
-        setDuration(ALL, duration);
-        setInterpolator(ALL, interpolator);
-        setListener(listener);
-    }
-
-    /**
-     * Creates a new {@link AnimatorSet} that will animate the given animators.  Callers need to
-     * manually apply the individual animation properties for each of the animators respectively.
-     */
-    public AnimatorSet createAnimator(List<Animator> animators) {
-        AnimatorSet anim = new AnimatorSet();
-        if (mListener != null) {
-            anim.addListener(mListener);
-        }
-        anim.playTogether(animators);
-        return anim;
-    }
-
-    /**
-     * Applies the specific start delay, duration and interpolator to the given {@param animator}
-     * for the specified {@param propertyType}.
-     */
-    public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) {
-        animator.setStartDelay(getStartDelay(propertyType));
-        animator.setDuration(getDuration(propertyType));
-        animator.setInterpolator(getInterpolator(propertyType));
-        return animator;
-    }
-
-    /**
-     * Sets a start delay for a specific property.
-     */
-    public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
-        if (mPropStartDelay == null) {
-            mPropStartDelay = new SparseLongArray();
-        }
-        mPropStartDelay.append(propertyType, startDelay);
-        return this;
-    }
-
-    /**
-     * Returns the start delay for a specific property.
-     */
-    public long getStartDelay(@PropType int propertyType) {
-        if (mPropStartDelay != null) {
-            long startDelay = mPropStartDelay.get(propertyType, -1);
-            if (startDelay != -1) {
-                return startDelay;
-            }
-            return mPropStartDelay.get(ALL, 0);
-        }
-        return 0;
-    }
-
-    /**
-     * Sets a duration for a specific property.
-     */
-    public AnimationProps setDuration(@PropType int propertyType, int duration) {
-        if (mPropDuration == null) {
-            mPropDuration = new SparseLongArray();
-        }
-        mPropDuration.append(propertyType, duration);
-        return this;
-    }
-
-    /**
-     * Returns the duration for a specific property.
-     */
-    public long getDuration(@PropType int propertyType) {
-        if (mPropDuration != null) {
-            long duration = mPropDuration.get(propertyType, -1);
-            if (duration != -1) {
-                return duration;
-            }
-            return mPropDuration.get(ALL, 0);
-        }
-        return 0;
-    }
-
-    /**
-     * Sets an interpolator for a specific property.
-     */
-    public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
-        if (mPropInterpolators == null) {
-            mPropInterpolators = new SparseArray<>();
-        }
-        mPropInterpolators.append(propertyType, interpolator);
-        return this;
-    }
-
-    /**
-     * Returns the interpolator for a specific property, falling back to the general interpolator
-     * if there is no specific property interpolator.
-     */
-    public Interpolator getInterpolator(@PropType int propertyType) {
-        if (mPropInterpolators != null) {
-            Interpolator interp = mPropInterpolators.get(propertyType);
-            if (interp != null) {
-                return interp;
-            }
-            return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR);
-        }
-        return LINEAR_INTERPOLATOR;
-    }
-
-    /**
-     * Sets an animator listener for this animation.
-     */
-    public AnimationProps setListener(Animator.AnimatorListener listener) {
-        mListener = listener;
-        return this;
-    }
-
-    /**
-     * Returns the animator listener for this animation.
-     */
-    public Animator.AnimatorListener getListener() {
-        return mListener;
-    }
-
-    /**
-     * Returns whether this animation has any duration.
-     */
-    public boolean isImmediate() {
-        int count = mPropDuration.size();
-        for (int i = 0; i < count; i++) {
-            if (mPropDuration.valueAt(i) > 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/Utilities.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/Utilities.java
deleted file mode 100644
index ff58eba..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/utilities/Utilities.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.utilities;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.RectEvaluator;
-import android.annotation.FloatRange;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Trace;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.Property;
-import android.util.TypedValue;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewRootImpl;
-import android.view.ViewStub;
-
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
-import java.util.ArrayList;
-import java.util.Collections;
-
-/* Common code */
-public class Utilities {
-
-    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
-            new IntProperty<Drawable>("drawableAlpha") {
-                @Override
-                public void setValue(Drawable object, int alpha) {
-                    object.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(Drawable object) {
-                    return object.getAlpha();
-                }
-            };
-
-    public static final Property<Drawable, Rect> DRAWABLE_RECT =
-            new Property<Drawable, Rect>(Rect.class, "drawableBounds") {
-                @Override
-                public void set(Drawable object, Rect bounds) {
-                    object.setBounds(bounds);
-                }
-
-                @Override
-                public Rect get(Drawable object) {
-                    return object.getBounds();
-                }
-            };
-
-    public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
-    public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
-
-    /**
-     * @return the first parent walking up the view hierarchy that has the given class type.
-     *
-     * @param parentClass must be a class derived from {@link View}
-     */
-    public static <T extends View> T findParent(View v, Class<T> parentClass) {
-        ViewParent parent = v.getParent();
-        while (parent != null) {
-            if (parentClass.isAssignableFrom(parent.getClass())) {
-                return (T) parent;
-            }
-            parent = parent.getParent();
-        }
-        return null;
-    }
-
-    /**
-     * Initializes the {@param setOut} with the given object.
-     */
-    public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) {
-        setOut.clear();
-        if (obj != null) {
-            setOut.add(obj);
-        }
-        return setOut;
-    }
-
-    /**
-     * Replaces the contents of {@param setOut} with the contents of the {@param array}.
-     */
-    public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) {
-        setOut.clear();
-        if (array != null) {
-            Collections.addAll(setOut, array);
-        }
-        return setOut;
-    }
-
-    /**
-     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
-     */
-    public static int clamp(int value, int min, int max) {
-        return Math.max(min, Math.min(max, value));
-    }
-
-    /**
-     * @return the clamped {@param value} between 0 and 1.
-     */
-    public static float clamp01(float value) {
-        return Math.max(0f, Math.min(1f, value));
-    }
-
-    /**
-     * Scales the {@param value} to be proportionally between the {@param min} and
-     * {@param max} values.
-     *
-     * @param value must be between 0 and 1
-     */
-    public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) {
-        return min + (value * (max - min));
-    }
-
-    /**
-     * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1.
-     *
-     * @param value must be between {@param min} and {@param max}
-     */
-    public static float unmapRange(float value, float min, float max) {
-        return (value - min) / (max - min);
-    }
-
-    /** Scales a rect about its centroid */
-    public static void scaleRectAboutCenter(RectF r, float scale) {
-        if (scale != 1.0f) {
-            float cx = r.centerX();
-            float cy = r.centerY();
-            r.offset(-cx, -cy);
-            r.left *= scale;
-            r.top *= scale;
-            r.right *= scale;
-            r.bottom *= scale;
-            r.offset(cx, cy);
-        }
-    }
-
-    /** Returns the base color overlaid with another overlay color with a specified alpha. */
-    public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
-        return Color.rgb(
-            (int) (overlayAlpha * Color.red(baseColor) +
-                    (1f - overlayAlpha) * Color.red(overlayColor)),
-            (int) (overlayAlpha * Color.green(baseColor) +
-                    (1f - overlayAlpha) * Color.green(overlayColor)),
-            (int) (overlayAlpha * Color.blue(baseColor) +
-                    (1f - overlayAlpha) * Color.blue(overlayColor)));
-    }
-
-    /**
-     * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
-     * are not called.
-     */
-    public static void cancelAnimationWithoutCallbacks(Animator animator) {
-        if (animator != null && animator.isStarted()) {
-            removeAnimationListenersRecursive(animator);
-            animator.cancel();
-        }
-    }
-
-    /**
-     * Recursively removes all the listeners of all children of this animator
-     */
-    public static void removeAnimationListenersRecursive(Animator animator) {
-        if (animator instanceof AnimatorSet) {
-            ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations();
-            for (int i = animators.size() - 1; i >= 0; i--) {
-                removeAnimationListenersRecursive(animators.get(i));
-            }
-        }
-        animator.removeAllListeners();
-    }
-
-    /**
-     * Sets the given {@link View}'s frame from its current translation.
-     */
-    public static void setViewFrameFromTranslation(View v) {
-        RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-        taskViewRect.offset(v.getTranslationX(), v.getTranslationY());
-        v.setTranslationX(0);
-        v.setTranslationY(0);
-        v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top,
-                (int) taskViewRect.right, (int) taskViewRect.bottom);
-    }
-
-    /**
-     * Returns a view stub for the given view id.
-     */
-    public static ViewStub findViewStubById(View v, int stubId) {
-        return (ViewStub) v.findViewById(stubId);
-    }
-
-    /**
-     * Returns a view stub for the given view id.
-     */
-    public static ViewStub findViewStubById(Activity a, int stubId) {
-        return (ViewStub) a.findViewById(stubId);
-    }
-
-    /**
-     * Used for debugging, converts DP to PX.
-     */
-    public static float dpToPx(Resources res, float dp) {
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
-    }
-
-    /**
-     * Adds a trace event for debugging.
-     */
-    public static void addTraceEvent(String event) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, event);
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-    }
-
-    /**
-     * Returns whether this view, or one of its descendants have accessibility focus.
-     */
-    public static boolean isDescendentAccessibilityFocused(View v) {
-        if (v.isAccessibilityFocused()) {
-            return true;
-        }
-
-        if (v instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) v;
-            int childCount = vg.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                if (isDescendentAccessibilityFocused(vg.getChildAt(i))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns the application configuration, which is independent of the activity's current
-     * configuration in multiwindow.
-     */
-    public static Configuration getAppConfiguration(Context context) {
-        return context.getApplicationContext().getResources().getConfiguration();
-    }
-
-    /**
-     * @return The next frame name for the specified surface or -1 if the surface is no longer
-     *         valid.
-     */
-    public static long getNextFrameNumber(Surface s) {
-        return s != null && s.isValid()
-                ? s.getNextFrameNumber()
-                : -1;
-
-    }
-
-    /**
-     * @return The surface for the specified view.
-     */
-    public static @Nullable Surface getSurface(View v) {
-        ViewRootImpl viewRoot = v.getViewRootImpl();
-        if (viewRoot == null) {
-            return null;
-        }
-        return viewRoot.mSurface;
-    }
-
-    /**
-     * Returns a lightweight dump of a rect.
-     */
-    public static String dumpRect(Rect r) {
-        if (r == null) {
-            return "N:0,0-0,0";
-        }
-        return r.left + "," + r.top + "-" + r.right + "," + r.bottom;
-    }
-
-    /**
-     * Posts a runnable on a handler at the front of the queue ignoring any sync barriers.
-     */
-    public static void postAtFrontOfQueueAsynchronously(Handler h, Runnable r) {
-        Message msg = h.obtainMessage().setCallback(r);
-        h.sendMessageAtFrontOfQueue(msg);
-    }
-
-    /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
-    public static float computeContrastBetweenColors(int bg, int fg) {
-        float bgR = Color.red(bg) / 255f;
-        float bgG = Color.green(bg) / 255f;
-        float bgB = Color.blue(bg) / 255f;
-        bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
-        bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
-        bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
-        float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
-
-        float fgR = Color.red(fg) / 255f;
-        float fgG = Color.green(fg) / 255f;
-        float fgB = Color.blue(fg) / 255f;
-        fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
-        fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
-        fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
-        float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
-
-        return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
-    }
-
-    /**
-     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
-     */
-    public static float clamp(float value, float min, float max) {
-        return Math.max(min, Math.min(max, value));
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/AnimateableViewBounds.java
deleted file mode 100644
index e188506..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.systemui.recents.utilities.Utilities;
-
-/**
- * An outline provider that has a clip and outline that can be animated.
- */
-public class AnimateableViewBounds extends ViewOutlineProvider {
-
-    private static final float MIN_ALPHA = 0.1f;
-    private static final float MAX_ALPHA = 0.8f;
-
-    protected View mSourceView;
-    protected Rect mClipRect = new Rect();
-    protected Rect mClipBounds = new Rect();
-    protected Rect mLastClipBounds = new Rect();
-    protected int mCornerRadius;
-    protected float mAlpha = 1f;
-
-    public AnimateableViewBounds(View source, int cornerRadius) {
-        mSourceView = source;
-        mCornerRadius = cornerRadius;
-    }
-
-    /**
-     * Resets the right and bottom clip for this view.
-     */
-    public void reset() {
-        mClipRect.set(0, 0, 0, 0);
-        updateClipBounds();
-    }
-
-    @Override
-    public void getOutline(View view, Outline outline) {
-        outline.setAlpha(Utilities.mapRange(mAlpha, MIN_ALPHA, MAX_ALPHA));
-        if (mCornerRadius > 0) {
-            outline.setRoundRect(mClipRect.left, mClipRect.top,
-                    mSourceView.getWidth() - mClipRect.right,
-                    mSourceView.getHeight() - mClipRect.bottom,
-                    mCornerRadius);
-        } else {
-            outline.setRect(mClipRect.left, mClipRect.top,
-                    mSourceView.getWidth() - mClipRect.right,
-                    mSourceView.getHeight() - mClipRect.bottom);
-        }
-    }
-
-    /**
-     * Sets the view outline alpha.
-     */
-    public void setAlpha(float alpha) {
-        if (Float.compare(alpha, mAlpha) != 0) {
-            mAlpha = alpha;
-            // TODO, If both clip and alpha change in the same frame, only invalidate once
-            mSourceView.invalidateOutline();
-        }
-    }
-
-    /**
-     * @return the outline alpha.
-     */
-    public float getAlpha() {
-        return mAlpha;
-    }
-
-    /**
-     * Sets the top clip.
-     */
-    public void setClipTop(int top) {
-        mClipRect.top = top;
-        updateClipBounds();
-    }
-
-    /**
-     * @return the top clip.
-     */
-    public int getClipTop() {
-        return mClipRect.top;
-    }
-
-    /**
-     * Sets the bottom clip.
-     */
-    public void setClipBottom(int bottom) {
-        mClipRect.bottom = bottom;
-        updateClipBounds();
-    }
-
-    /**
-     * @return the bottom clip.
-     */
-    public int getClipBottom() {
-        return mClipRect.bottom;
-    }
-
-    /**
-     * @return the clip bounds.
-     */
-    public Rect getClipBounds() {
-        return mClipBounds;
-    }
-
-    protected void updateClipBounds() {
-        mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
-                mSourceView.getWidth() - Math.max(0, mClipRect.right),
-                mSourceView.getHeight() - Math.max(0, mClipRect.bottom));
-        if (!mLastClipBounds.equals(mClipBounds)) {
-            mSourceView.setClipBounds(mClipBounds);
-            // TODO, If both clip and alpha change in the same frame, only invalidate once
-            mSourceView.invalidateOutline();
-            mLastClipBounds.set(mClipBounds);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DockState.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DockState.java
deleted file mode 100644
index d9c921c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DockState.java
+++ /dev/null
@@ -1,351 +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.recents.views;
-
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.util.IntProperty;
-import android.view.animation.Interpolator;
-
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.utilities.Utilities;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-
-/**
- * The various possible dock states when dragging and dropping a task.
- */
-public class DockState implements DropTarget {
-
-    public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
-    public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
-
-    // The rotation to apply to the hint text
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({HORIZONTAL, VERTICAL})
-    public @interface TextOrientation {}
-    private static final int HORIZONTAL = 0;
-    private static final int VERTICAL = 1;
-
-    private static final int DOCK_AREA_ALPHA = 80;
-    public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
-            null, null, null);
-    public static final DockState LEFT = new DockState(DOCKED_LEFT,
-            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
-            new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
-            new RectF(0, 0, 0.5f, 1));
-    public static final DockState TOP = new DockState(DOCKED_TOP,
-            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-            new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
-            new RectF(0, 0, 1, 0.5f));
-    public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
-            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
-            new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
-            new RectF(0.5f, 0, 1, 1));
-    public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
-            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-            new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
-            new RectF(0, 0.5f, 1, 1));
-
-    @Override
-    public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-            boolean isCurrentTarget) {
-        if (isCurrentTarget) {
-            getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
-            return mTmpRect.contains(x, y);
-        } else {
-            getMappedRect(touchArea, width, height, mTmpRect);
-            updateBoundsWithSystemInsets(mTmpRect, insets);
-            return mTmpRect.contains(x, y);
-        }
-    }
-
-    // Represents the view state of this dock state
-    public static class ViewState {
-        private static final IntProperty<ViewState> HINT_ALPHA =
-                new IntProperty<ViewState>("drawableAlpha") {
-                    @Override
-                    public void setValue(ViewState object, int alpha) {
-                        object.mHintTextAlpha = alpha;
-                        object.dockAreaOverlay.invalidateSelf();
-                    }
-
-                    @Override
-                    public Integer get(ViewState object) {
-                        return object.mHintTextAlpha;
-                    }
-                };
-
-        public final int dockAreaAlpha;
-        public final ColorDrawable dockAreaOverlay;
-        public final int hintTextAlpha;
-        public final int hintTextOrientation;
-
-        private final int mHintTextResId;
-        private String mHintText;
-        private Paint mHintTextPaint;
-        private Point mHintTextBounds = new Point();
-        private int mHintTextAlpha = 255;
-        private AnimatorSet mDockAreaOverlayAnimator;
-        private Rect mTmpRect = new Rect();
-
-        private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
-                int hintTextResId) {
-            dockAreaAlpha = areaAlpha;
-            dockAreaOverlay = new ColorDrawable(LegacyRecentsImpl.getConfiguration().isGridEnabled
-                    ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
-            dockAreaOverlay.setAlpha(0);
-            hintTextAlpha = hintAlpha;
-            hintTextOrientation = hintOrientation;
-            mHintTextResId = hintTextResId;
-            mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-            mHintTextPaint.setColor(Color.WHITE);
-        }
-
-        /**
-         * Updates the view state with the given context.
-         */
-        public void update(Context context) {
-            Resources res = context.getResources();
-            mHintText = context.getString(mHintTextResId);
-            mHintTextPaint.setTextSize(res.getDimensionPixelSize(
-                    R.dimen.recents_drag_hint_text_size));
-            mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
-            mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
-        }
-
-        /**
-         * Draws the current view state.
-         */
-        public void draw(Canvas canvas) {
-            // Draw the overlay background
-            if (dockAreaOverlay.getAlpha() > 0) {
-                dockAreaOverlay.draw(canvas);
-            }
-
-            // Draw the hint text
-            if (mHintTextAlpha > 0) {
-                Rect bounds = dockAreaOverlay.getBounds();
-                int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
-                int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
-                mHintTextPaint.setAlpha(mHintTextAlpha);
-                if (hintTextOrientation == VERTICAL) {
-                    canvas.save();
-                    canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
-                }
-                canvas.drawText(mHintText, x, y, mHintTextPaint);
-                if (hintTextOrientation == VERTICAL) {
-                    canvas.restore();
-                }
-            }
-        }
-
-        /**
-         * Creates a new bounds and alpha animation.
-         */
-        public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
-                Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
-            if (mDockAreaOverlayAnimator != null) {
-                mDockAreaOverlayAnimator.cancel();
-            }
-
-            ObjectAnimator anim;
-            ArrayList<Animator> animators = new ArrayList<>();
-            if (dockAreaOverlay.getAlpha() != areaAlpha) {
-                if (animateAlpha) {
-                    anim = ObjectAnimator.ofInt(dockAreaOverlay,
-                            Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
-                    anim.setDuration(duration);
-                    anim.setInterpolator(interpolator);
-                    animators.add(anim);
-                } else {
-                    dockAreaOverlay.setAlpha(areaAlpha);
-                }
-            }
-            if (mHintTextAlpha != hintAlpha) {
-                if (animateAlpha) {
-                    anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
-                            hintAlpha);
-                    anim.setDuration(150);
-                    anim.setInterpolator(hintAlpha > mHintTextAlpha
-                            ? Interpolators.ALPHA_IN
-                            : Interpolators.ALPHA_OUT);
-                    animators.add(anim);
-                } else {
-                    mHintTextAlpha = hintAlpha;
-                    dockAreaOverlay.invalidateSelf();
-                }
-            }
-            if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
-                if (animateBounds) {
-                    PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
-                            Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
-                            new Rect(dockAreaOverlay.getBounds()), bounds);
-                    anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
-                    anim.setDuration(duration);
-                    anim.setInterpolator(interpolator);
-                    animators.add(anim);
-                } else {
-                    dockAreaOverlay.setBounds(bounds);
-                }
-            }
-            if (!animators.isEmpty()) {
-                mDockAreaOverlayAnimator = new AnimatorSet();
-                mDockAreaOverlayAnimator.playTogether(animators);
-                mDockAreaOverlayAnimator.start();
-            }
-        }
-    }
-
-    public final int dockSide;
-    public final int createMode;
-    public final ViewState viewState;
-    private final RectF touchArea;
-    private final RectF dockArea;
-    private final RectF expandedTouchDockArea;
-    private static final Rect mTmpRect = new Rect();
-
-    /**
-     * @param createMode used to pass to ActivityManager to dock the task
-     * @param touchArea the area in which touch will initiate this dock state
-     * @param dockArea the visible dock area
-     * @param expandedTouchDockArea the area in which touch will continue to dock after entering
-     *                              the initial touch area.  This is also the new dock area to
-     *                              draw.
-     */
-    DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
-            @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
-            RectF expandedTouchDockArea) {
-        this.dockSide = dockSide;
-        this.createMode = createMode;
-        this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
-                R.string.recents_drag_hint_message);
-        this.dockArea = dockArea;
-        this.touchArea = touchArea;
-        this.expandedTouchDockArea = expandedTouchDockArea;
-    }
-
-    /**
-     * Updates the dock state with the given context.
-     */
-    public void update(Context context) {
-        viewState.update(context);
-    }
-
-    /**
-     * Returns the docked task bounds with the given {@param width} and {@param height}.
-     */
-    public Rect getPreDockedBounds(int width, int height, Rect insets) {
-        getMappedRect(dockArea, width, height, mTmpRect);
-        return updateBoundsWithSystemInsets(mTmpRect, insets);
-    }
-
-    /**
-     * Returns the expanded docked task bounds with the given {@param width} and
-     * {@param height}.
-     */
-    public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
-            Resources res) {
-        // Calculate the docked task bounds
-        boolean isHorizontalDivision =
-                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                insets, width, height, dividerSize);
-        Rect newWindowBounds = new Rect();
-        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
-                width, height, dividerSize);
-        return newWindowBounds;
-    }
-
-    /**
-     * Returns the task stack bounds with the given {@param width} and
-     * {@param height}.
-     */
-    public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
-            int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
-            Resources res, Rect windowRectOut) {
-        // Calculate the inverse docked task bounds
-        boolean isHorizontalDivision =
-                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                insets, width, height, dividerSize);
-        DockedDividerUtils.calculateBoundsForPosition(position,
-                DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
-                dividerSize);
-
-        // Calculate the task stack bounds from the new window bounds
-        Rect taskStackBounds = new Rect();
-        // If the task stack bounds is specifically under the dock area, then ignore the top
-        // inset
-        int top = dockArea.bottom < 1f
-                ? 0
-                : insets.top;
-        // For now, ignore the left insets since we always dock on the left and show Recents
-        // on the right
-        layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
-                taskStackBounds);
-        return taskStackBounds;
-    }
-
-    /**
-     * Returns the expanded bounds in certain dock sides such that the bounds account for the
-     * system insets (namely the vertical nav bar).  This call modifies and returns the given
-     * {@param bounds}.
-     */
-    private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
-        if (dockSide == DOCKED_LEFT) {
-            bounds.right += insets.left;
-        } else if (dockSide == DOCKED_RIGHT) {
-            bounds.left -= insets.right;
-        }
-        return bounds;
-    }
-
-    /**
-     * Returns the mapped rect to the given dimensions.
-     */
-    private void getMappedRect(RectF bounds, int width, int height, Rect out) {
-        out.set((int) (bounds.left * width), (int) (bounds.top * height),
-                (int) (bounds.right * width), (int) (bounds.bottom * height));
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DropTarget.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DropTarget.java
deleted file mode 100644
index f2a6310..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/DropTarget.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.graphics.Rect;
-
-/**
- * Represents a drop target for a drag gesture.
- */
-public interface DropTarget {
-
-    /**
-     * Returns whether this target can accept this drop.  The x,y are relative to the top level
-     * RecentsView, and the width/height are of the RecentsView.
-     */
-    boolean acceptsDrop(int x, int y, int width, int height, Rect insets, boolean isCurrentTarget);
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FakeShadowDrawable.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FakeShadowDrawable.java
deleted file mode 100644
index 86b4297..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FakeShadowDrawable.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.recents.views;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PixelFormat;
-import android.graphics.RadialGradient;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsConfiguration;
-
-/**
- * A rounded rectangle drawable which also includes a shadow around. This is mostly copied from
- * frameworks/support/v7/cardview/eclair-mr1/android/support/v7/widget/
- * RoundRectDrawableWithShadow.java revision c42ba8c000d1e6ce85e152dfc17089a0a69e739f with a few
- * modifications to suit our needs in SystemUI.
- */
-class FakeShadowDrawable extends Drawable {
-    // used to calculate content padding
-    final static double COS_45 = Math.cos(Math.toRadians(45));
-
-    final static float SHADOW_MULTIPLIER = 1.5f;
-
-    final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
-
-    Paint mCornerShadowPaint;
-
-    Paint mEdgeShadowPaint;
-
-    final RectF mCardBounds;
-
-    float mCornerRadius;
-
-    Path mCornerShadowPath;
-
-    // updated value with inset
-    float mMaxShadowSize;
-
-    // actual value set by developer
-    float mRawMaxShadowSize;
-
-    // multiplied value to account for shadow offset
-    float mShadowSize;
-
-    // actual value set by developer
-    float mRawShadowSize;
-
-    private boolean mDirty = true;
-
-    private final int mShadowStartColor;
-
-    private final int mShadowEndColor;
-
-    private boolean mAddPaddingForCorners = true;
-
-    /**
-     * If shadow size is set to a value above max shadow, we print a warning
-     */
-    private boolean mPrintedShadowClipWarning = false;
-
-    public FakeShadowDrawable(Resources resources, RecentsConfiguration config) {
-        mShadowStartColor = resources.getColor(R.color.fake_shadow_start_color);
-        mShadowEndColor = resources.getColor(R.color.fake_shadow_end_color);
-        mInsetShadow = resources.getDimension(R.dimen.fake_shadow_inset);
-        setShadowSize(resources.getDimensionPixelSize(R.dimen.fake_shadow_size),
-                resources.getDimensionPixelSize(R.dimen.fake_shadow_size));
-        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
-        mCornerShadowPaint.setStyle(Paint.Style.FILL);
-        mCornerShadowPaint.setDither(true);
-        mCornerRadius = LegacyRecentsImpl.getConfiguration().isGridEnabled ?
-                resources.getDimensionPixelSize(
-                    R.dimen.recents_grid_task_view_rounded_corners_radius) :
-                resources.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
-        mCardBounds = new RectF();
-        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mCornerShadowPaint.setAlpha(alpha);
-        mEdgeShadowPaint.setAlpha(alpha);
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        super.onBoundsChange(bounds);
-        mDirty = true;
-    }
-
-    void setShadowSize(float shadowSize, float maxShadowSize) {
-        if (shadowSize < 0 || maxShadowSize < 0) {
-            throw new IllegalArgumentException("invalid shadow size");
-        }
-        if (shadowSize > maxShadowSize) {
-            shadowSize = maxShadowSize;
-            if (!mPrintedShadowClipWarning) {
-                Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
-                        + "{CardView#setMaxCardElevation}.");
-                mPrintedShadowClipWarning = true;
-            }
-        }
-        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
-            return;
-        }
-        mRawShadowSize = shadowSize;
-        mRawMaxShadowSize = maxShadowSize;
-        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
-        mMaxShadowSize = maxShadowSize + mInsetShadow;
-        mDirty = true;
-        invalidateSelf();
-    }
-
-    @Override
-    public boolean getPadding(Rect padding) {
-        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
-                mAddPaddingForCorners));
-        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
-                mAddPaddingForCorners));
-        padding.set(hOffset, vOffset, hOffset, vOffset);
-        return true;
-    }
-
-    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
-            boolean addPaddingForCorners) {
-        if (addPaddingForCorners) {
-            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
-        } else {
-            return maxShadowSize * SHADOW_MULTIPLIER;
-        }
-    }
-
-    static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
-            boolean addPaddingForCorners) {
-        if (addPaddingForCorners) {
-            return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
-        } else {
-            return maxShadowSize;
-        }
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        mCornerShadowPaint.setColorFilter(colorFilter);
-        mEdgeShadowPaint.setColorFilter(colorFilter);
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.OPAQUE;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mDirty) {
-            buildComponents(getBounds());
-            mDirty = false;
-        }
-        canvas.translate(0, mRawShadowSize / 4);
-        drawShadow(canvas);
-        canvas.translate(0, -mRawShadowSize / 4);
-    }
-
-    private void drawShadow(Canvas canvas) {
-        final float edgeShadowTop = -mCornerRadius - mShadowSize;
-        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
-        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
-        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
-        // LT
-        int saved = canvas.save();
-        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
-        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
-        if (drawHorizontalEdges) {
-            canvas.drawRect(0, edgeShadowTop,
-                    mCardBounds.width() - 2 * inset, -mCornerRadius,
-                    mEdgeShadowPaint);
-        }
-        canvas.restoreToCount(saved);
-        // RB
-        saved = canvas.save();
-        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
-        canvas.rotate(180f);
-        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
-        if (drawHorizontalEdges) {
-            canvas.drawRect(0, edgeShadowTop,
-                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
-                    mEdgeShadowPaint);
-        }
-        canvas.restoreToCount(saved);
-        // LB
-        saved = canvas.save();
-        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
-        canvas.rotate(270f);
-        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
-        if (drawVerticalEdges) {
-            canvas.drawRect(0, edgeShadowTop,
-                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
-        }
-        canvas.restoreToCount(saved);
-        // RT
-        saved = canvas.save();
-        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
-        canvas.rotate(90f);
-        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
-        if (drawVerticalEdges) {
-            canvas.drawRect(0, edgeShadowTop,
-                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
-        }
-        canvas.restoreToCount(saved);
-    }
-
-    private void buildShadowCorners() {
-        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
-        RectF outerBounds = new RectF(innerBounds);
-        outerBounds.inset(-mShadowSize, -mShadowSize);
-
-        if (mCornerShadowPath == null) {
-            mCornerShadowPath = new Path();
-        } else {
-            mCornerShadowPath.reset();
-        }
-        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
-        mCornerShadowPath.moveTo(-mCornerRadius, 0);
-        mCornerShadowPath.rLineTo(-mShadowSize, 0);
-        // outer arc
-        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
-        // inner arc
-        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
-        mCornerShadowPath.close();
-
-        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
-        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
-                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
-                new float[]{0f, startRatio, 1f}
-                , Shader.TileMode.CLAMP));
-
-        // we offset the content shadowSize/2 pixels up to make it more realistic.
-        // this is why edge shadow shader has some extra space
-        // When drawing bottom edge shadow, we use that extra space.
-        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
-                -mCornerRadius - mShadowSize,
-                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
-                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
-    }
-
-    private void buildComponents(Rect bounds) {
-        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
-        // We could have different top-bottom offsets to avoid extra gap above but in that case
-        // center aligning Views inside the CardView would be problematic.
-        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
-        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
-                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
-        buildShadowCorners();
-    }
-
-    float getMinWidth() {
-        final float content = 2 *
-                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
-        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
-    }
-
-    float getMinHeight() {
-        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
-                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
-        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java
deleted file mode 100644
index 471df6a..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java
+++ /dev/null
@@ -1,97 +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.recents.views;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/**
- * This is an optimized FrameLayout whose layout is completely directed by its parent, and as a
- * result, does not propagate <code>requestLayout()</code> up the view hierarchy. Instead, it will
- * relayout its children with the last known layout bounds when a layout is requested from a child
- * view.
- */
-public class FixedSizeFrameLayout extends FrameLayout {
-
-    private final Rect mLayoutBounds = new Rect();
-
-    public FixedSizeFrameLayout(Context context) {
-        super(context);
-    }
-
-    public FixedSizeFrameLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        measureContents(MeasureSpec.getSize(widthMeasureSpec),
-                MeasureSpec.getSize(heightMeasureSpec));
-    }
-
-    @Override
-    protected final void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        mLayoutBounds.set(left, top, right, bottom);
-        layoutContents(mLayoutBounds, changed);
-    }
-
-    @Override
-    public final void requestLayout() {
-        // The base ViewGroup constructor attempts to call requestLayout() before this class's
-        // members are initialized so we should just propagate in that case
-        if (mLayoutBounds == null || mLayoutBounds.isEmpty()) {
-            super.requestLayout();
-        } else {
-            // If we are already laid out, then just reuse the same bounds to layout the children
-            // (but not itself)
-            // TODO: Investigate whether we should coalesce these to the next frame if needed
-            measureContents(getMeasuredWidth(), getMeasuredHeight());
-            layoutContents(mLayoutBounds, false);
-        }
-    }
-
-    /**
-     * Measures the contents of this fixed layout.
-     */
-    protected void measureContents(int width, int height) {
-        super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-    }
-
-    /**
-     * Lays out the contents of this fixed layout.
-     */
-    protected void layoutContents(Rect bounds, boolean changed) {
-        super.onLayout(changed, bounds.left, bounds.top, bounds.right, bounds.bottom);
-
-        int width = getMeasuredWidth();
-        int height = getMeasuredHeight();
-        onSizeChanged(width, height, width, height);
-    }
-
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeImageView.java
deleted file mode 100644
index d3b5e47..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/FixedSizeImageView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-import com.android.systemui.statusbar.AlphaOptimizedImageView;
-
-/**
- * This is an optimized ImageView that does not trigger a <code>requestLayout()</code> or
- * <code>invalidate()</code> when setting the image to <code>null</code>.
- */
-public class FixedSizeImageView extends AlphaOptimizedImageView {
-
-    private boolean mAllowRelayout = true;
-    private boolean mAllowInvalidate = true;
-
-    public FixedSizeImageView(Context context) {
-        this(context, null);
-    }
-
-    public FixedSizeImageView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public FixedSizeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    public void requestLayout() {
-        if (mAllowRelayout) {
-            super.requestLayout();
-        }
-    }
-
-    @Override
-    public void invalidate() {
-        if (mAllowInvalidate) {
-            super.invalidate();
-        }
-    }
-
-    @Override
-    public void setImageDrawable(Drawable drawable) {
-        boolean isNullBitmapDrawable = (drawable instanceof BitmapDrawable) &&
-                (((BitmapDrawable) drawable).getBitmap() == null);
-        if (drawable == null || isNullBitmapDrawable) {
-            mAllowRelayout = false;
-            mAllowInvalidate = false;
-        }
-        super.setImageDrawable(drawable);
-        mAllowRelayout = true;
-        mAllowInvalidate = true;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsEntrancePathInterpolator.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsEntrancePathInterpolator.java
deleted file mode 100644
index e32da2d..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsEntrancePathInterpolator.java
+++ /dev/null
@@ -1,47 +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.recents.views;
-
-import android.view.animation.PathInterpolator;
-
-/**
- * A helper interpolator to stagger the entrance animation in recents by offsetting the start time
- */
-public class RecentsEntrancePathInterpolator extends PathInterpolator {
-    final float mStartOffsetFraction;
-
-    /**
-     * Create an interpolator for a cubic Bezier curve with an offset play time. The end points
-     * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
-     *
-     * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
-     * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
-     * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
-     * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
-     * @param startOffsetFraction The fraction from 0 to 1 to start the animation from
-     */
-    public RecentsEntrancePathInterpolator(float controlX1, float controlY1, float controlX2,
-            float controlY2, float startOffsetFraction) {
-        super(controlX1, controlY1, controlX2, controlY2);
-        mStartOffsetFraction = startOffsetFraction;
-    }
-
-    @Override
-    public float getInterpolation(float t) {
-        return super.getInterpolation(t + mStartOffsetFraction);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsTransitionComposer.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
deleted file mode 100644
index ce66318..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsTransitionComposer.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.RecentsTransition;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create the transition app animation specs to/from Recents
- */
-public class RecentsTransitionComposer {
-
-    private static final String TAG = "RecentsTransitionComposer";
-
-    private Context mContext;
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
-    public RecentsTransitionComposer(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link TaskView}
-     */
-    private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView,
-            TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
-        Bitmap b = null;
-        if (addHeaderBitmap) {
-            b = composeHeaderBitmap(taskView, transform);
-            if (b == null) {
-                return null;
-            }
-        }
-
-        Rect taskRect = new Rect();
-        transform.rect.round(taskRect);
-        // Disable in for low ram devices because each task does in Recents does not have fullscreen
-        // height (stackView height) and when transitioning to fullscreen app, the code below would
-        // force the task thumbnail to full stackView height immediately causing the transition
-        // jarring.
-        if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice && taskView.getTask() !=
-                stackView.getStack().getFrontMostTask()) {
-            taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
-        }
-        return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect);
-    }
-
-    /**
-     * Composes the transition spec when docking a task, which includes a full task bitmap.
-     */
-    public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView,
-            Rect bounds) {
-        mTmpTransform.fillIn(taskView);
-        Task task = taskView.getTask();
-        Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
-        return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer,
-                bounds));
-    }
-
-    /**
-     * Composes the animation specs for all the tasks in the target stack.
-     */
-    public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        // Calculate the offscreen task rect (for tasks that are not backed by views)
-        TaskView taskView = stackView.getChildViewForTask(task);
-        TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
-        Rect offscreenTaskRect = new Rect();
-        stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
-        // If this is a full screen stack, the transition will be towards the single, full screen
-        // task. We only need the transition spec for this task.
-
-        // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
-        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
-        if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                || activityType == ACTIVITY_TYPE_ASSISTANT
-                || windowingMode == WINDOWING_MODE_UNDEFINED) {
-            List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>();
-            if (taskView == null) {
-                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
-            } else {
-                mTmpTransform.fillIn(taskView);
-                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
-                AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView,
-                        mTmpTransform, true /* addHeaderBitmap */);
-                if (spec != null) {
-                    specs.add(spec);
-                }
-            }
-            return specs;
-        }
-        return Collections.emptyList();
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link Task}
-     */
-    private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task,
-            Rect taskRect) {
-        return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect);
-    }
-
-    public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
-        float scale = transform.scale;
-        int fromWidth = (int) (transform.rect.width() * scale);
-        int fromHeight = (int) (transform.rect.height() * scale);
-        if (fromWidth == 0 || fromHeight == 0) {
-            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
-                    " at transform: " + transform);
-
-            return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff);
-        } else {
-            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f,
-                        0xFFff0000);
-            } else {
-                return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView,
-                        scale, 0);
-            }
-        }
-    }
-
-    private static Bitmap composeHeaderBitmap(TaskView taskView,
-            TaskViewTransform transform) {
-        float scale = transform.scale;
-        int headerWidth = (int) (transform.rect.width());
-        int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
-        if (headerWidth == 0 || headerHeight == 0) {
-            return null;
-        }
-
-        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-            return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f,
-                    0xFFff0000);
-        } else {
-            return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight,
-                    taskView.mHeaderView, scale, 0);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
deleted file mode 100644
index e60ffba..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsView.java
+++ /dev/null
@@ -1,1077 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-
-import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewPropertyAnimator;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
-import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
-import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
-import com.android.systemui.recents.events.component.ExpandPipEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
-import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
-import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
-import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.stackdivider.WindowManagerProxy;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.phone.ScrimController;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This view is the the top level layout that contains TaskStacks (which are laid out according
- * to their SpaceNode bounds.
- */
-public class RecentsView extends FrameLayout {
-
-    private static final String TAG = "RecentsView";
-
-    private static final int DEFAULT_UPDATE_SCRIM_DURATION = 200;
-
-    private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
-    private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
-
-    private static final int BUSY_RECENTS_TASK_COUNT = 3;
-
-    private Handler mHandler;
-    private TaskStackView mTaskStackView;
-    private TextView mStackActionButton;
-    private TextView mEmptyView;
-    private final float mStackButtonShadowRadius;
-    private final PointF mStackButtonShadowDistance;
-    private final int mStackButtonShadowColor;
-
-    private boolean mAwaitingFirstLayout = true;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    Rect mSystemInsets = new Rect();
-    private int mDividerSize;
-
-    private float mBusynessFactor;
-    private ScrimDrawable mBackgroundScrim;
-    private ColorDrawable mMultiWindowBackgroundScrim;
-    private ValueAnimator mBackgroundScrimAnimator;
-    private Point mTmpDisplaySize = new Point();
-
-    private final AnimatorUpdateListener mUpdateBackgroundScrimAlpha = (animation) -> {
-        int alpha = (Integer) animation.getAnimatedValue();
-        mBackgroundScrim.setAlpha(alpha);
-        mMultiWindowBackgroundScrim.setAlpha(alpha);
-    };
-
-    private RecentsTransitionComposer mTransitionHelper;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
-    private RecentsViewTouchHandler mTouchHandler;
-    private final FlingAnimationUtils mFlingAnimationUtils;
-
-    public RecentsView(Context context) {
-        this(context, null);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        setWillNotDraw(false);
-
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        mHandler = new Handler();
-        mTransitionHelper = new RecentsTransitionComposer(getContext());
-        mDividerSize = ssp.getDockedDividerSize(context);
-        mTouchHandler = new RecentsViewTouchHandler(this);
-        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
-        mBackgroundScrim = new ScrimDrawable();
-        mMultiWindowBackgroundScrim = new ColorDrawable();
-
-        LayoutInflater inflater = LayoutInflater.from(context);
-        mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
-        addView(mEmptyView);
-
-        if (mStackActionButton != null) {
-            removeView(mStackActionButton);
-        }
-        mStackActionButton = (TextView) inflater.inflate(LegacyRecentsImpl.getConfiguration()
-                        .isLowRamDevice
-                    ? R.layout.recents_low_ram_stack_action_button
-                    : R.layout.recents_stack_action_button,
-                this, false);
-
-        mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
-        mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
-                mStackActionButton.getShadowDy());
-        mStackButtonShadowColor = mStackActionButton.getShadowColor();
-        addView(mStackActionButton);
-
-        reevaluateStyles();
-    }
-
-    public void reevaluateStyles() {
-        int textColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
-        boolean usingDarkText = Color.luminance(textColor) < 0.5f;
-
-        mEmptyView.setTextColor(textColor);
-        mEmptyView.setCompoundDrawableTintList(new ColorStateList(new int[][]{
-                {android.R.attr.state_enabled}}, new int[]{textColor}));
-
-        if (mStackActionButton != null) {
-            mStackActionButton.setTextColor(textColor);
-            // Enable/disable shadow if text color is already dark.
-            if (usingDarkText) {
-                mStackActionButton.setShadowLayer(0, 0, 0, 0);
-            } else {
-                mStackActionButton.setShadowLayer(mStackButtonShadowRadius,
-                        mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
-                        mStackButtonShadowColor);
-            }
-        }
-
-        // Let's also require dark status and nav bars if the text is dark
-        int systemBarsStyle = usingDarkText ? View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
-                View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0;
-
-        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
-                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
-                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
-                systemBarsStyle);
-    }
-
-    /**
-     * Called from RecentsActivity when it is relaunched.
-     */
-    public void onReload(TaskStack stack, boolean isResumingFromVisible) {
-        final RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        final RecentsActivityLaunchState launchState = config.getLaunchState();
-        final boolean isTaskStackEmpty = stack.getTaskCount() == 0;
-
-        if (mTaskStackView == null) {
-            isResumingFromVisible = false;
-            mTaskStackView = new TaskStackView(getContext());
-            mTaskStackView.setSystemInsets(mSystemInsets);
-            addView(mTaskStackView);
-        }
-
-        // Reset the state
-        mAwaitingFirstLayout = !isResumingFromVisible;
-
-        // Update the stack
-        mTaskStackView.onReload(isResumingFromVisible);
-        updateStack(stack, true /* setStackViewTasks */);
-        updateBusyness();
-
-        if (isResumingFromVisible) {
-            // If we are already visible, then restore the background scrim
-            animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
-        } else {
-            // If we are already occluded by the app, then set the final background scrim alpha now.
-            // Otherwise, defer until the enter animation completes to animate the scrim alpha with
-            // the tasks for the home animation.
-            if (launchState.launchedViaDockGesture || launchState.launchedFromApp
-                    || isTaskStackEmpty) {
-                mBackgroundScrim.setAlpha((int) (getOpaqueScrimAlpha() * 255));
-            } else {
-                mBackgroundScrim.setAlpha(0);
-            }
-            mMultiWindowBackgroundScrim.setAlpha(mBackgroundScrim.getAlpha());
-        }
-    }
-
-    /**
-     * Called from RecentsActivity when the task stack is updated.
-     */
-    public void updateStack(TaskStack stack, boolean setStackViewTasks) {
-        if (setStackViewTasks) {
-            mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
-        }
-
-        // Update the top level view's visibilities
-        if (stack.getTaskCount() > 0) {
-            hideEmptyView();
-        } else {
-            showEmptyView(R.string.recents_empty_message);
-        }
-    }
-
-    /**
-     * Animates the scrim opacity based on how many tasks are visible.
-     * Called from {@link RecentsActivity} when tasks are dismissed.
-     */
-    public void updateScrimOpacity() {
-        if (updateBusyness()) {
-            animateBackgroundScrim(getOpaqueScrimAlpha(), DEFAULT_UPDATE_SCRIM_DURATION);
-        }
-    }
-
-    /**
-     * Updates the busyness factor.
-     *
-     * @return True if it changed.
-     */
-    private boolean updateBusyness() {
-        final int taskCount = mTaskStackView.getStack().getTaskCount();
-        final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
-                / (float) BUSY_RECENTS_TASK_COUNT;
-        if (mBusynessFactor == busyness) {
-            return false;
-        } else {
-            mBusynessFactor = busyness;
-            return true;
-        }
-    }
-
-    /**
-     * Returns the current TaskStack.
-     */
-    public TaskStack getStack() {
-        return mTaskStackView.getStack();
-    }
-
-    /**
-     * Returns the window background scrim.
-     */
-    public void updateBackgroundScrim(Window window, boolean isInMultiWindow) {
-        if (isInMultiWindow) {
-            mBackgroundScrim.setCallback(null);
-            window.setBackgroundDrawable(mMultiWindowBackgroundScrim);
-        } else {
-            mMultiWindowBackgroundScrim.setCallback(null);
-            window.setBackgroundDrawable(mBackgroundScrim);
-        }
-    }
-
-    /** Launches the focused task from the first stack if possible */
-    public boolean launchFocusedTask(int logEvent) {
-        if (mTaskStackView != null) {
-            Task task = mTaskStackView.getFocusedTask();
-            if (task != null) {
-                TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
-
-                if (logEvent != 0) {
-                    MetricsLogger.action(getContext(), logEvent,
-                            task.key.getComponent().toString());
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Launches the task that recents was launched from if possible */
-    public boolean launchPreviousTask() {
-        if (LegacyRecentsImpl.getConfiguration().getLaunchState().launchedFromPipApp) {
-            // If the app auto-entered PiP on the way to Recents, then just re-expand it
-            EventBus.getDefault().send(new ExpandPipEvent());
-            return true;
-        }
-
-        if (mTaskStackView != null) {
-            Task task = getStack().getLaunchTarget();
-            if (task != null) {
-                TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Hides the task stack and shows the empty view.
-     */
-    public void showEmptyView(int msgResId) {
-        mTaskStackView.setVisibility(View.INVISIBLE);
-        mEmptyView.setText(msgResId);
-        mEmptyView.setVisibility(View.VISIBLE);
-        mEmptyView.bringToFront();
-        mStackActionButton.bringToFront();
-    }
-
-    /**
-     * Shows the task stack and hides the empty view.
-     */
-    public void hideEmptyView() {
-        mEmptyView.setVisibility(View.INVISIBLE);
-        mTaskStackView.setVisibility(View.VISIBLE);
-        mTaskStackView.bringToFront();
-        mStackActionButton.bringToFront();
-    }
-
-    /**
-     * Set the color of the scrim.
-     *
-     * @param scrimColors Colors to use.
-     * @param animated Interpolate colors if true.
-     */
-    public void setScrimColors(ColorExtractor.GradientColors scrimColors, boolean animated) {
-        mBackgroundScrim.setColor(scrimColors.getMainColor(), animated);
-        int alpha = mMultiWindowBackgroundScrim.getAlpha();
-        mMultiWindowBackgroundScrim.setColor(scrimColors.getMainColor());
-        mMultiWindowBackgroundScrim.setAlpha(alpha);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 2);
-        super.onAttachedToWindow();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        EventBus.getDefault().unregister(this);
-        EventBus.getDefault().unregister(mTouchHandler);
-    }
-
-    /**
-     * This is called with the full size of the window since we are handling our own insets.
-     */
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (mTaskStackView.getVisibility() != GONE) {
-            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
-        }
-
-        // Measure the empty view to the full size of the screen
-        if (mEmptyView.getVisibility() != GONE) {
-            measureChild(mEmptyView, MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-        }
-
-        // Measure the stack action button within the constraints of the space above the stack
-        Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
-        measureChild(mStackActionButton,
-                MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
-                MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
-
-        setMeasuredDimension(width, height);
-    }
-
-    /**
-     * This is called with the full size of the window since we are handling our own insets.
-     */
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (mTaskStackView.getVisibility() != GONE) {
-            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
-        }
-
-        // Layout the empty view
-        if (mEmptyView.getVisibility() != GONE) {
-            int leftRightInsets = mSystemInsets.left + mSystemInsets.right;
-            int topBottomInsets = mSystemInsets.top + mSystemInsets.bottom;
-            int childWidth = mEmptyView.getMeasuredWidth();
-            int childHeight = mEmptyView.getMeasuredHeight();
-            int childLeft = left + mSystemInsets.left +
-                    Math.max(0, (right - left - leftRightInsets - childWidth)) / 2;
-            int childTop = top + mSystemInsets.top +
-                    Math.max(0, (bottom - top - topBottomInsets - childHeight)) / 2;
-            mEmptyView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
-        }
-
-        // Needs to know the screen size since the gradient never scales up or down
-        // even when bounds change.
-        mContext.getDisplay().getRealSize(mTmpDisplaySize);
-        mBackgroundScrim.setBounds(left, top, right, bottom);
-        mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
-
-        // Layout the stack action button such that its drawable is start-aligned with the
-        // stack, vertically centered in the available space above the stack
-        Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
-        mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
-                buttonBounds.bottom);
-
-        if (mAwaitingFirstLayout) {
-            mAwaitingFirstLayout = false;
-            // If launched via dragging from the nav bar, then we should translate the whole view
-            // down offscreen
-            RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-            if (launchState.launchedViaDragGesture) {
-                setTranslationY(getMeasuredHeight());
-            } else {
-                setTranslationY(0f);
-            }
-
-            if (LegacyRecentsImpl.getConfiguration().isLowRamDevice
-                    && mEmptyView.getVisibility() == View.VISIBLE) {
-                animateEmptyView(true /* show */, null /* postAnimationTrigger */);
-            }
-        }
-    }
-
-    @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        mSystemInsets.set(insets.getSystemWindowInsetsAsRect());
-        mTaskStackView.setSystemInsets(mSystemInsets);
-        requestLayout();
-        return insets;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return mTouchHandler.onInterceptTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return mTouchHandler.onTouchEvent(ev);
-    }
-
-    @Override
-    public void onDrawForeground(Canvas canvas) {
-        super.onDrawForeground(canvas);
-
-        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
-        for (int i = visDockStates.size() - 1; i >= 0; i--) {
-            visDockStates.get(i).viewState.draw(canvas);
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
-        for (int i = visDockStates.size() - 1; i >= 0; i--) {
-            Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
-            if (d == who) {
-                return true;
-            }
-        }
-        return super.verifyDrawable(who);
-    }
-
-    /**** EventBus Events ****/
-
-    public final void onBusEvent(LaunchTaskEvent event) {
-        launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
-                event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
-        }
-    }
-
-    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
-        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        // Hide the stack action button
-        EventBus.getDefault().send(new HideStackActionButtonEvent());
-        animateBackgroundScrim(0f, taskViewExitToHomeDuration);
-
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            animateEmptyView(false /* show */, event.getAnimationTrigger());
-        }
-    }
-
-    public final void onBusEvent(DragStartEvent event) {
-        updateVisibleDockRegions(LegacyRecentsImpl.getConfiguration().getDockStatesForCurrentOrientation(),
-                true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
-                DockState.NONE.viewState.hintTextAlpha,
-                true /* animateAlpha */, false /* animateBounds */);
-
-        // Temporarily hide the stack action button without changing visibility
-        if (mStackActionButton != null) {
-            mStackActionButton.animate()
-                    .alpha(0f)
-                    .setDuration(HIDE_STACK_ACTION_BUTTON_DURATION)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .start();
-        }
-    }
-
-    public final void onBusEvent(DragDropTargetChangedEvent event) {
-        if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
-            updateVisibleDockRegions(
-                    LegacyRecentsImpl.getConfiguration().getDockStatesForCurrentOrientation(),
-                    true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
-                    DockState.NONE.viewState.hintTextAlpha,
-                    true /* animateAlpha */, true /* animateBounds */);
-        } else {
-            final DockState dockState = (DockState) event.dropTarget;
-            updateVisibleDockRegions(new DockState[] {dockState},
-                    false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
-                    true /* animateBounds */);
-        }
-        if (mStackActionButton != null) {
-            event.addPostAnimationCallback(new Runnable() {
-                @Override
-                public void run() {
-                    // Move the clear all button to its new position
-                    Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
-                    mStackActionButton.setLeftTopRightBottom(buttonBounds.left, buttonBounds.top,
-                            buttonBounds.right, buttonBounds.bottom);
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(final DragEndEvent event) {
-        // Handle the case where we drop onto a dock region
-        if (event.dropTarget instanceof DockState) {
-            final DockState dockState = (DockState) event.dropTarget;
-
-            // Hide the dock region
-            updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
-                    false /* animateAlpha */, false /* animateBounds */);
-
-            // We translated the view but we need to animate it back from the current layout-space
-            // rect to its final layout-space rect
-            Utilities.setViewFrameFromTranslation(event.taskView);
-
-            final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
-                    dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
-            if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
-                    options)) {
-                final Runnable animStartedListener = () -> {
-                    EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
-                    // Remove the task and don't bother relaying out, as all the tasks
-                    // will be relaid out when the stack changes on the multiwindow
-                    // change event
-                    getStack().removeTask(event.task, null, true /* fromDockGesture */);
-                };
-                final Rect taskRect = getTaskRect(event.taskView);
-                AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
-                        getHandler()) {
-                    @Override
-                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                        return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
-                    }
-                };
-                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
-                        future, animStartedListener, getHandler(), true /* scaleUp */,
-                        getContext().getDisplayId());
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
-                        event.task.getTopComponent().flattenToShortString());
-            } else {
-                EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
-                        event.taskView));
-            }
-        } else {
-            // Animate the overlay alpha back to 0
-            updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
-                    true /* animateAlpha */, false /* animateBounds */);
-        }
-
-        // Show the stack action button again without changing visibility
-        if (mStackActionButton != null) {
-            mStackActionButton.animate()
-                    .alpha(1f)
-                    .setDuration(SHOW_STACK_ACTION_BUTTON_DURATION)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .start();
-        }
-    }
-
-    public final void onBusEvent(final DragEndCancelledEvent event) {
-        // Animate the overlay alpha back to 0
-        updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
-                true /* animateAlpha */, false /* animateBounds */);
-    }
-
-    private Rect getTaskRect(TaskView taskView) {
-        int[] location = taskView.getLocationOnScreen();
-        int viewX = location[0];
-        int viewY = location[1];
-        return new Rect(viewX, viewY,
-                (int) (viewX + taskView.getWidth() * taskView.getScaleX()),
-                (int) (viewY + taskView.getHeight() * taskView.getScaleY()));
-    }
-
-    public final void onBusEvent(DraggingInRecentsEvent event) {
-        if (mTaskStackView.getTaskViews().size() > 0) {
-            setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
-        }
-    }
-
-    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
-        ViewPropertyAnimator animator = animate();
-        if (event.velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            animator.translationY(getHeight());
-            animator.withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    WindowManagerProxy.getInstance().maximizeDockedStack();
-                }
-            });
-            mFlingAnimationUtils.apply(animator, getTranslationY(), getHeight(), event.velocity);
-        } else {
-            animator.translationY(0f);
-            animator.setListener(null);
-            mFlingAnimationUtils.apply(animator, getTranslationY(), 0, event.velocity);
-        }
-        animator.start();
-    }
-
-    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-        if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
-                && getStack().getTaskCount() > 0) {
-            animateBackgroundScrim(getOpaqueScrimAlpha(),
-                    TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
-        }
-    }
-
-    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        EventBus.getDefault().send(new HideStackActionButtonEvent());
-    }
-
-    public final void onBusEvent(DismissAllTaskViewsEvent event) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        if (!ssp.hasDockedTask()) {
-            // Animate the background away only if we are dismissing Recents to home
-            animateBackgroundScrim(0f, DEFAULT_UPDATE_SCRIM_DURATION);
-        }
-    }
-
-    public final void onBusEvent(ShowStackActionButtonEvent event) {
-        showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
-    }
-
-    public final void onBusEvent(HideStackActionButtonEvent event) {
-        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
-    }
-
-    public final void onBusEvent(MultiWindowStateChangedEvent event) {
-        updateStack(event.stack, false /* setStackViewTasks */);
-    }
-
-    public final void onBusEvent(ShowEmptyViewEvent event) {
-        showEmptyView(R.string.recents_empty_message);
-    }
-
-    /**
-     * Shows the stack action button.
-     */
-    private void showStackActionButton(final int duration, final boolean translate) {
-        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
-        if (mStackActionButton.getVisibility() == View.INVISIBLE) {
-            mStackActionButton.setVisibility(View.VISIBLE);
-            mStackActionButton.setAlpha(0f);
-            if (translate) {
-                mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
-                        (LegacyRecentsImpl.getConfiguration().isLowRamDevice ? 1 : -0.25f));
-            } else {
-                mStackActionButton.setTranslationY(0f);
-            }
-            postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    if (translate) {
-                        mStackActionButton.animate()
-                            .translationY(0f);
-                    }
-                    mStackActionButton.animate()
-                            .alpha(1f)
-                            .setDuration(duration)
-                            .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                            .start();
-                }
-            });
-        }
-        postAnimationTrigger.flushLastDecrementRunnables();
-    }
-
-    /**
-     * Hides the stack action button.
-     */
-    private void hideStackActionButton(int duration, boolean translate) {
-        final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
-        hideStackActionButton(duration, translate, postAnimationTrigger);
-        postAnimationTrigger.flushLastDecrementRunnables();
-    }
-
-    /**
-     * Hides the stack action button.
-     */
-    private void hideStackActionButton(int duration, boolean translate,
-                                       final ReferenceCountedTrigger postAnimationTrigger) {
-        if (mStackActionButton.getVisibility() == View.VISIBLE) {
-            if (translate) {
-                mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
-                        * (LegacyRecentsImpl.getConfiguration().isLowRamDevice ? 1 : -0.25f));
-            }
-            mStackActionButton.animate()
-                    .alpha(0f)
-                    .setDuration(duration)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mStackActionButton.setVisibility(View.INVISIBLE);
-                            postAnimationTrigger.decrement();
-                        }
-                    })
-                    .start();
-            postAnimationTrigger.increment();
-        }
-    }
-
-    /**
-     * Animates a translation in the Y direction and fades in/out for empty view to show or hide it.
-     * @param show whether to translate up and fade in the empty view to the center of the screen
-     * @param postAnimationTrigger to keep track of the animation
-     */
-    private void animateEmptyView(boolean show, ReferenceCountedTrigger postAnimationTrigger) {
-        float start = mTaskStackView.getStackAlgorithm().getTaskRect().height() / 4;
-        mEmptyView.setTranslationY(show ? start : 0);
-        mEmptyView.setAlpha(show ? 0f : 1f);
-        ViewPropertyAnimator animator = mEmptyView.animate()
-                .setDuration(150)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .translationY(show ? 0 : start)
-                .alpha(show ? 1f : 0f);
-
-        if (postAnimationTrigger != null) {
-            animator.setListener(postAnimationTrigger.decrementOnAnimationEnd());
-            postAnimationTrigger.increment();
-        }
-        animator.start();
-    }
-
-    /**
-     * Updates the dock region to match the specified dock state.
-     */
-    private void updateVisibleDockRegions(DockState[] newDockStates,
-            boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
-            boolean animateAlpha, boolean animateBounds) {
-        ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
-                new ArraySet<DockState>());
-        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
-        for (int i = visDockStates.size() - 1; i >= 0; i--) {
-            DockState dockState = visDockStates.get(i);
-            DockState.ViewState viewState = dockState.viewState;
-            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
-                // This is no longer visible, so hide it
-                viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
-                        Interpolators.FAST_OUT_SLOW_IN, animateAlpha, animateBounds);
-            } else {
-                // This state is now visible, update the bounds and show it
-                int areaAlpha = overrideAreaAlpha != -1
-                        ? overrideAreaAlpha
-                        : viewState.dockAreaAlpha;
-                int hintAlpha = overrideHintAlpha != -1
-                        ? overrideHintAlpha
-                        : viewState.hintTextAlpha;
-                Rect bounds = isDefaultDockState
-                        ? dockState.getPreDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
-                                mSystemInsets)
-                        : dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight(),
-                                mDividerSize, mSystemInsets, getResources());
-                if (viewState.dockAreaOverlay.getCallback() != this) {
-                    viewState.dockAreaOverlay.setCallback(this);
-                    viewState.dockAreaOverlay.setBounds(bounds);
-                }
-                viewState.startAnimation(bounds, areaAlpha, hintAlpha,
-                        TaskStackView.SLOW_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
-                        animateAlpha, animateBounds);
-            }
-        }
-    }
-
-    /**
-     * Scrim alpha based on how busy recents is:
-     * Scrim will be {@link ScrimController#GRADIENT_SCRIM_ALPHA} when the stack is empty,
-     * and {@link ScrimController#GRADIENT_SCRIM_ALPHA_BUSY} when it's full.
-     *
-     * @return Alpha from 0 to 1.
-     */
-    private float getOpaqueScrimAlpha() {
-        return MathUtils.map(0, 1, ScrimController.GRADIENT_SCRIM_ALPHA,
-                ScrimController.GRADIENT_SCRIM_ALPHA_BUSY, mBusynessFactor);
-    }
-
-    /**
-     * Animates the background scrim to the given {@param alpha}.
-     */
-    private void animateBackgroundScrim(float alpha, int duration) {
-        Utilities.cancelAnimationWithoutCallbacks(mBackgroundScrimAnimator);
-        // Calculate the absolute alpha to animate from
-        final int fromAlpha = mBackgroundScrim.getAlpha();
-        final int toAlpha = (int) (alpha * 255);
-        mBackgroundScrimAnimator = ValueAnimator.ofInt(fromAlpha, toAlpha);
-        mBackgroundScrimAnimator.setDuration(duration);
-        mBackgroundScrimAnimator.setInterpolator(toAlpha > fromAlpha
-                ? Interpolators.ALPHA_IN
-                : Interpolators.ALPHA_OUT);
-        mBackgroundScrimAnimator.addUpdateListener(mUpdateBackgroundScrimAlpha);
-        mBackgroundScrimAnimator.start();
-    }
-
-    /**
-     * @return the bounds of the stack action button.
-     */
-    Rect getStackActionButtonBoundsFromStackLayout() {
-        Rect actionButtonRect = new Rect(
-                mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
-        int left, top;
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            Rect windowRect = LegacyRecentsImpl.getSystemServices().getWindowRect();
-            int spaceLeft = windowRect.width() - mSystemInsets.left - mSystemInsets.right;
-            left = (spaceLeft - mStackActionButton.getMeasuredWidth()) / 2 + mSystemInsets.left;
-            top = windowRect.height() - (mStackActionButton.getMeasuredHeight()
-                    + mSystemInsets.bottom + mStackActionButton.getPaddingBottom() / 2);
-        } else {
-            left = isLayoutRtl()
-                ? actionButtonRect.left - mStackActionButton.getPaddingLeft()
-                : actionButtonRect.right + mStackActionButton.getPaddingRight()
-                        - mStackActionButton.getMeasuredWidth();
-            top = actionButtonRect.top +
-                (actionButtonRect.height() - mStackActionButton.getMeasuredHeight()) / 2;
-        }
-        actionButtonRect.set(left, top, left + mStackActionButton.getMeasuredWidth(),
-                top + mStackActionButton.getMeasuredHeight());
-        return actionButtonRect;
-    }
-
-    View getStackActionButton() {
-        return mStackActionButton;
-    }
-
-    /**
-     * Launches the specified {@link Task}.
-     */
-    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
-            final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
-
-        final Runnable animStartedListener;
-        final AppTransitionAnimationSpecsFuture transitionFuture;
-        if (taskView != null) {
-
-            // Fetch window rect here already in order not to be blocked on lock contention in WM
-            // when the future calls it.
-            final Rect windowRect = LegacyRecentsImpl.getSystemServices().getWindowRect();
-            transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
-                @Override
-                public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                    return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
-                            activityType, windowRect);
-                }
-            };
-            animStartedListener = new Runnable() {
-                private boolean mHandled;
-
-                @Override
-                public void run() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (screenPinningRequested) {
-                        // Request screen pinning after the animation runs
-                        mHandler.postDelayed(() -> {
-                            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
-                                    task.key.id));
-                        }, 350);
-                    }
-
-                    if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        } else {
-            // This is only the case if the task is not on screen (scrolled offscreen for example)
-            transitionFuture = null;
-            animStartedListener = new Runnable() {
-                private boolean mHandled;
-
-                @Override
-                public void run() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        }
-
-        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
-        final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
-                mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
-                animStartedListener);
-        if (taskView == null) {
-            // If there is no task view, then we do not need to worry about animating out occluding
-            // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                    windowingMode, activityType);
-        } else {
-            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
-                    screenPinningRequested);
-            EventBus.getDefault().send(launchStartedEvent);
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
-                    activityType);
-        }
-        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
-    }
-
-    /**
-     * Starts the activity for the launch task.
-     *
-     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
-     *                 we are toggling recents and the launch-to task is now offscreen.
-     */
-    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
-            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
-            int windowingMode, int activityType) {
-        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
-                windowingMode, activityType, succeeded -> {
-            if (succeeded) {
-                // Keep track of the index of the task launch
-                int taskIndexFromFront = 0;
-                int taskIndex = stack.indexOfTask(task);
-                if (taskIndex > -1) {
-                    taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
-                }
-                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
-            } else {
-                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
-
-                // Dismiss the task if we fail to launch it
-                if (taskView != null) {
-                    taskView.dismissTask();
-                }
-
-                // Keep track of failed launches
-                EventBus.getDefault().send(new LaunchTaskFailedEvent());
-            }
-        }, getHandler());
-        if (transitionFuture != null) {
-            mHandler.post(transitionFuture::composeSpecsSynchronous);
-        }
-    }
-
-    @Override
-    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        super.requestDisallowInterceptTouchEvent(disallowIntercept);
-        mTouchHandler.cancelStackActionButtonClick();
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-        String id = Integer.toHexString(System.identityHashCode(this));
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
-        writer.print(" insets="); writer.print(Utilities.dumpRect(mSystemInsets));
-        writer.print(" [0x"); writer.print(id); writer.print("]");
-        writer.println();
-
-        if (getStack() != null) {
-            getStack().dump(innerPrefix, writer);
-        }
-        if (mTaskStackView != null) {
-            mTaskStackView.dump(innerPrefix, writer);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
deleted file mode 100644
index 1a827d5..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.app.ActivityTaskManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewDebug;
-
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
-import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
-import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.ArrayList;
-
-/**
- * Handles touch events for a RecentsView.
- */
-public class RecentsViewTouchHandler {
-
-    private RecentsView mRv;
-
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task")
-    private Task mDragTask;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task_view_")
-    private TaskView mTaskView;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    private Point mTaskViewOffset = new Point();
-    @ViewDebug.ExportedProperty(category="recents")
-    private Point mDownPos = new Point();
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mDragRequested;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mIsDragging;
-    private float mDragSlop;
-    private int mDeviceId = -1;
-
-    private DropTarget mLastDropTarget;
-    private DividerSnapAlgorithm mDividerSnapAlgorithm;
-    private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
-    private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
-
-    public RecentsViewTouchHandler(RecentsView rv) {
-        mRv = rv;
-        mDragSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
-        updateSnapAlgorithm();
-    }
-
-    private void updateSnapAlgorithm() {
-        Rect insets = new Rect();
-        SystemServicesProxy.getInstance(mRv.getContext()).getStableInsets(insets);
-        mDividerSnapAlgorithm = DividerSnapAlgorithm.create(mRv.getContext(), insets);
-    }
-
-    /**
-     * Registers a new drop target for the current drag only.
-     */
-    public void registerDropTargetForCurrentDrag(DropTarget target) {
-        mDropTargets.add(target);
-    }
-
-    /**
-     * Returns the set of visible dock states for this current drag.
-     */
-    public ArrayList<DockState> getVisibleDockStates() {
-        return mVisibleDockStates;
-    }
-
-    /** Touch preprocessing for handling below */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return handleTouchEvent(ev) || mDragRequested;
-    }
-
-    /** Handles touch events once we have intercepted them */
-    public boolean onTouchEvent(MotionEvent ev) {
-        handleTouchEvent(ev);
-        if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) {
-            EventBus.getDefault().send(new HideRecentsEvent(false, true));
-        }
-        return true;
-    }
-
-    /**** Events ****/
-
-    public final void onBusEvent(DragStartEvent event) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        mRv.getParent().requestDisallowInterceptTouchEvent(true);
-        mDragRequested = true;
-        // We defer starting the actual drag handling until the user moves past the drag slop
-        mIsDragging = false;
-        mDragTask = event.task;
-        mTaskView = event.taskView;
-        mDropTargets.clear();
-
-        int[] recentsViewLocation = new int[2];
-        mRv.getLocationInWindow(recentsViewLocation);
-        mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
-                mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
-
-        // Change space coordinates relative to the view to RecentsView when user initiates a touch
-        if (event.isUserTouchInitiated) {
-            float x = mDownPos.x - mTaskViewOffset.x;
-            float y = mDownPos.y - mTaskViewOffset.y;
-            mTaskView.setTranslationX(x);
-            mTaskView.setTranslationY(y);
-        }
-
-        mVisibleDockStates.clear();
-        if (ActivityTaskManager.supportsMultiWindow(mRv.getContext()) && !ssp.hasDockedTask()
-                && mDividerSnapAlgorithm.isSplitScreenFeasible()) {
-            LegacyRecentsImpl.logDockAttempt(mRv.getContext(), event.task.getTopComponent(),
-                    event.task.resizeMode);
-            if (!event.task.isDockable) {
-                EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
-            } else {
-                // Add the dock state drop targets (these take priority)
-                DockState[] dockStates = LegacyRecentsImpl.getConfiguration()
-                        .getDockStatesForCurrentOrientation();
-                for (DockState dockState : dockStates) {
-                    registerDropTargetForCurrentDrag(dockState);
-                    dockState.update(mRv.getContext());
-                    mVisibleDockStates.add(dockState);
-                }
-            }
-        }
-
-        // Request other drop targets to register themselves
-        EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task,
-                event.taskView, this));
-        if (mDeviceId != -1) {
-            InputDevice device = InputDevice.getDevice(mDeviceId);
-            if (device != null) {
-                device.setPointerType(PointerIcon.TYPE_GRABBING);
-            }
-        }
-    }
-
-    public final void onBusEvent(DragEndEvent event) {
-        if (!mDragTask.isDockable) {
-            EventBus.getDefault().send(new HideIncompatibleAppOverlayEvent());
-        }
-        mDragRequested = false;
-        mDragTask = null;
-        mTaskView = null;
-        mLastDropTarget = null;
-    }
-
-    public final void onBusEvent(ConfigurationChangedEvent event) {
-        if (event.fromDisplayDensityChange || event.fromDeviceOrientationChange) {
-            updateSnapAlgorithm();
-        }
-    }
-
-    void cancelStackActionButtonClick() {
-        mRv.getStackActionButton().setPressed(false);
-    }
-
-    private boolean isWithinStackActionButton(float x, float y) {
-        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
-        return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
-                mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
-    }
-
-    private void changeStackActionButtonDrawableHotspot(float x, float y) {
-        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
-        mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
-    }
-
-    /**
-     * Handles dragging touch events
-     */
-    private boolean handleTouchEvent(MotionEvent ev) {
-        int action = ev.getActionMasked();
-        boolean consumed = false;
-        float evX = ev.getX();
-        float evY = ev.getY();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                mDownPos.set((int) evX, (int) evY);
-                mDeviceId = ev.getDeviceId();
-
-                if (isWithinStackActionButton(evX, evY)) {
-                    changeStackActionButtonDrawableHotspot(evX, evY);
-                    mRv.getStackActionButton().setPressed(true);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE: {
-                float x = evX - mTaskViewOffset.x;
-                float y = evY - mTaskViewOffset.y;
-
-                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
-                    changeStackActionButtonDrawableHotspot(evX, evY);
-                }
-
-                if (mDragRequested) {
-                    if (!mIsDragging) {
-                        mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
-                    }
-                    if (mIsDragging) {
-                        int width = mRv.getMeasuredWidth();
-                        int height = mRv.getMeasuredHeight();
-
-                        DropTarget currentDropTarget = null;
-
-                        // Give priority to the current drop target to retain the touch handling
-                        if (mLastDropTarget != null) {
-                            if (mLastDropTarget.acceptsDrop((int) evX, (int) evY, width, height,
-                                    mRv.mSystemInsets, true /* isCurrentTarget */)) {
-                                currentDropTarget = mLastDropTarget;
-                            }
-                        }
-
-                        // Otherwise, find the next target to handle this event
-                        if (currentDropTarget == null) {
-                            for (DropTarget target : mDropTargets) {
-                                if (target.acceptsDrop((int) evX, (int) evY, width, height,
-                                        mRv.mSystemInsets, false /* isCurrentTarget */)) {
-                                    currentDropTarget = target;
-                                    break;
-                                }
-                            }
-                        }
-                        if (mLastDropTarget != currentDropTarget) {
-                            mLastDropTarget = currentDropTarget;
-                            EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
-                                    currentDropTarget));
-                        }
-                    }
-                    mTaskView.setTranslationX(x);
-                    mTaskView.setTranslationY(y);
-                }
-                break;
-            }
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL: {
-                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
-                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
-                    consumed = true;
-                }
-                cancelStackActionButtonClick();
-                if (mDragRequested) {
-                    boolean cancelled = action == MotionEvent.ACTION_CANCEL;
-                    if (cancelled) {
-                        EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask, null));
-                    }
-                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
-                            !cancelled ? mLastDropTarget : null));
-                    break;
-                }
-                mDeviceId = -1;
-            }
-        }
-        return consumed;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/SystemBarScrimViews.java
deleted file mode 100644
index 22c12b4..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
-import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.utilities.AnimationProps;
-
-/** Manages the scrims for the various system bars. */
-public class SystemBarScrimViews {
-
-    private static final int DEFAULT_ANIMATION_DURATION = 150;
-
-    private Context mContext;
-
-    private View mNavBarScrimView;
-
-    private boolean mHasNavBarScrim;
-    private boolean mShouldAnimateNavBarScrim;
-    private boolean mHasTransposedNavBar;
-    private boolean mHasDockedTasks;
-    private int mNavBarScrimEnterDuration;
-
-    public SystemBarScrimViews(RecentsActivity activity) {
-        mContext = activity;
-        mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
-        mNavBarScrimView.forceHasOverlappingRendering(false);
-        mNavBarScrimEnterDuration = activity.getResources().getInteger(
-                R.integer.recents_nav_bar_scrim_enter_duration);
-        mHasNavBarScrim = LegacyRecentsImpl.getSystemServices().hasTransposedNavigationBar();
-        mHasDockedTasks = LegacyRecentsImpl.getSystemServices().hasDockedTask();
-    }
-
-    /**
-     * Updates the nav bar scrim.
-     */
-    public void updateNavBarScrim(boolean animateNavBarScrim, boolean hasStackTasks,
-            AnimationProps animation) {
-        prepareEnterRecentsAnimation(isNavBarScrimRequired(hasStackTasks), animateNavBarScrim);
-        if (animateNavBarScrim && animation != null) {
-            animateNavBarScrimVisibility(true, animation);
-        }
-    }
-
-    /**
-     * Prepares the scrim views for animating when entering Recents. This will be called before
-     * the first draw, unless we are updating the scrim on configuration change.
-     */
-    private void prepareEnterRecentsAnimation(boolean hasNavBarScrim, boolean animateNavBarScrim) {
-        mHasNavBarScrim = hasNavBarScrim;
-        mShouldAnimateNavBarScrim = animateNavBarScrim;
-
-        mNavBarScrimView.setVisibility(mHasNavBarScrim && !mShouldAnimateNavBarScrim ?
-                View.VISIBLE : View.INVISIBLE);
-    }
-
-    /**
-     * Animates the nav bar scrim visibility.
-     */
-    private void animateNavBarScrimVisibility(boolean visible, AnimationProps animation) {
-        int toY = 0;
-        if (visible) {
-            mNavBarScrimView.setVisibility(View.VISIBLE);
-            mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
-        } else {
-            toY = mNavBarScrimView.getMeasuredHeight();
-        }
-        if (animation != AnimationProps.IMMEDIATE) {
-            mNavBarScrimView.animate()
-                    .translationY(toY)
-                    .setDuration(animation.getDuration(AnimationProps.BOUNDS))
-                    .setInterpolator(animation.getInterpolator(AnimationProps.BOUNDS))
-                    .start();
-        } else {
-            mNavBarScrimView.setTranslationY(toY);
-        }
-    }
-
-    /**
-     * @return Whether to show the nav bar scrim.
-     */
-    private boolean isNavBarScrimRequired(boolean hasStackTasks) {
-        return hasStackTasks && !mHasTransposedNavBar && !mHasDockedTasks;
-    }
-
-    /**** EventBus events ****/
-
-    /**
-     * Starts animating the scrim views when entering Recents.
-     */
-    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
-        if (mHasNavBarScrim) {
-            AnimationProps animation = mShouldAnimateNavBarScrim
-                    ? new AnimationProps()
-                            .setDuration(AnimationProps.BOUNDS, mNavBarScrimEnterDuration)
-                            .setInterpolator(AnimationProps.BOUNDS, Interpolators.DECELERATE_QUINT)
-                    : AnimationProps.IMMEDIATE;
-            animateNavBarScrimVisibility(true, animation);
-        }
-    }
-
-    /**
-     * Starts animating the scrim views when leaving Recents (either via launching a task, or
-     * going home).
-     */
-    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
-        if (mHasNavBarScrim) {
-            AnimationProps animation = createBoundsAnimation(
-                    TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION);
-            animateNavBarScrimVisibility(false, animation);
-        }
-    }
-
-    public final void onBusEvent(DismissAllTaskViewsEvent event) {
-        if (mHasNavBarScrim) {
-            AnimationProps animation = createBoundsAnimation(
-                    TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION);
-            animateNavBarScrimVisibility(false, animation);
-        }
-    }
-
-    public final void onBusEvent(ConfigurationChangedEvent event) {
-        if (event.fromDeviceOrientationChange) {
-            mHasNavBarScrim = LegacyRecentsImpl.getSystemServices().hasTransposedNavigationBar();
-        }
-        animateScrimToCurrentNavBarState(event.hasStackTasks);
-    }
-
-    public final void onBusEvent(MultiWindowStateChangedEvent event) {
-        mHasDockedTasks = event.inMultiWindow;
-        animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
-    }
-
-    public final void onBusEvent(final DragEndEvent event) {
-        // Hide the nav bar scrims once we drop to a dock region
-        if (event.dropTarget instanceof DockState) {
-            animateScrimToCurrentNavBarState(false /* hasStackTasks */);
-        }
-    }
-
-    public final void onBusEvent(final DragEndCancelledEvent event) {
-        // Restore the scrims to the normal state
-        animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
-    }
-
-    /**
-     * Animates the scrim to match the state of the current nav bar.
-     */
-    private void animateScrimToCurrentNavBarState(boolean hasStackTasks) {
-        boolean hasNavBarScrim = isNavBarScrimRequired(hasStackTasks);
-        if (mHasNavBarScrim != hasNavBarScrim) {
-            AnimationProps animation = hasNavBarScrim
-                    ? createBoundsAnimation(DEFAULT_ANIMATION_DURATION)
-                    : AnimationProps.IMMEDIATE;
-            animateNavBarScrimVisibility(hasNavBarScrim, animation);
-        }
-        mHasNavBarScrim = hasNavBarScrim;
-    }
-
-    /**
-     * @return a default animation to aniamte the bounds of the scrim.
-     */
-    private AnimationProps createBoundsAnimation(int duration) {
-        return new AnimationProps()
-                .setDuration(AnimationProps.BOUNDS, duration)
-                .setInterpolator(AnimationProps.BOUNDS, Interpolators.FAST_OUT_SLOW_IN);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
deleted file mode 100644
index 5574934..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ /dev/null
@@ -1,705 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.Log;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.utilities.AnimationProps;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView},
- * but not the contents of the {@link TaskView}s.
- */
-public class TaskStackAnimationHelper {
-
-    /**
-     * Callbacks from the helper to coordinate view-content animations with view animations.
-     */
-    public interface Callbacks {
-        /**
-         * Callback to prepare for the start animation for the launch target {@link TaskView}.
-         */
-        void onPrepareLaunchTargetForEnterAnimation();
-
-        /**
-         * Callback to start the animation for the launch target {@link TaskView}.
-         */
-        void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
-                boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger);
-
-        /**
-         * Callback to start the animation for the launch target {@link TaskView} when it is
-         * launched from Recents.
-         */
-        void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
-                ReferenceCountedTrigger postAnimationTrigger);
-
-        /**
-         * Callback to start the animation for the front {@link TaskView} if there is no launch
-         * target.
-         */
-        void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled);
-    }
-
-    private static final int DOUBLE_FRAME_OFFSET_MS = 33;
-    private static final int FRAME_OFFSET_MS = 16;
-
-    private static final int ENTER_EXIT_NUM_ANIMATING_TASKS = 5;
-
-    private static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
-    public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 300;
-    private static final Interpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = Interpolators.LINEAR;
-
-    public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 200;
-    private static final Interpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
-            new PathInterpolator(0.4f, 0, 0.6f, 1f);
-
-    private static final int DISMISS_TASK_DURATION = 175;
-    private static final int DISMISS_ALL_TASKS_DURATION = 200;
-    private static final Interpolator DISMISS_ALL_TRANSLATION_INTERPOLATOR =
-            new PathInterpolator(0.4f, 0, 1f, 1f);
-
-    private static final Interpolator FOCUS_NEXT_TASK_INTERPOLATOR =
-            new PathInterpolator(0.4f, 0, 0, 1f);
-    private static final Interpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
-            new PathInterpolator(0, 0, 0, 1f);
-    private static final Interpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
-            Interpolators.LINEAR_OUT_SLOW_IN;
-
-    private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR =
-            Interpolators.LINEAR_OUT_SLOW_IN;
-
-    private final int mEnterAndExitFromHomeTranslationOffset;
-    private TaskStackView mStackView;
-
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-    private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
-    private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
-
-    public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
-        mStackView = stackView;
-        mEnterAndExitFromHomeTranslationOffset = LegacyRecentsImpl.getConfiguration().isGridEnabled
-                ? 0 : DOUBLE_FRAME_OFFSET_MS;
-    }
-
-    /**
-     * Prepares the stack views and puts them in their initial animation state while visible, before
-     * the in-app enter animations start (after the window-transition completes).
-     */
-    public void prepareForEnterAnimation() {
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        Resources res = mStackView.getResources();
-        Resources appResources = mStackView.getContext().getApplicationContext().getResources();
-
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-        TaskStack stack = mStackView.getStack();
-        Task launchTargetTask = stack.getLaunchTarget();
-
-        // Break early if there are no tasks
-        if (stack.getTaskCount() == 0) {
-            return;
-        }
-
-        int offscreenYOffset = stackLayout.mStackRect.height();
-        int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
-        int launchedWhileDockingOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_animation_launched_while_docking_offset);
-        boolean isLandscape = appResources.getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
-
-        float top = 0;
-        final boolean isLowRamDevice = LegacyRecentsImpl.getConfiguration().isLowRamDevice;
-        if (isLowRamDevice && launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
-            stackLayout.getStackTransform(launchTargetTask, stackScroller.getStackScroll(),
-                    mTmpTransform, null /* frontTransform */);
-            top = mTmpTransform.rect.top;
-        }
-
-        // Prepare each of the task views for their enter animation from front to back
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        for (int i = taskViews.size() - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            // Get the current transform for the task, which will be used to position it offscreen
-            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
-
-            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
-                if (task.isLaunchTarget) {
-                    tv.onPrepareLaunchTargetForEnterAnimation();
-                } else if (isLowRamDevice && i >= taskViews.size() -
-                            (TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT + 1)
-                        && !RecentsDebugFlags.Static.DisableRecentsLowRamEnterExitAnimation) {
-                    // Move the last 2nd and 3rd last tasks in-app animation to match the motion of
-                    // the last task's app transition
-                    stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
-                            mTmpTransform, null);
-                    mTmpTransform.rect.offset(0, -top);
-                    mTmpTransform.alpha = 0f;
-                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
-                            AnimationProps.IMMEDIATE);
-                    stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
-                            mTmpTransform, null);
-                    mTmpTransform.alpha = 1f;
-                    // Duration see {@link
-                    // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
-                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
-                            new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
-                }
-            } else if (launchState.launchedFromHome) {
-                if (isLowRamDevice) {
-                    mTmpTransform.rect.offset(0, stackLayout.getTaskRect().height() / 4);
-                } else {
-                    // Move the task view off screen (below) so we can animate it in
-                    mTmpTransform.rect.offset(0, offscreenYOffset);
-                }
-                mTmpTransform.alpha = 0f;
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
-            } else if (launchState.launchedViaDockGesture) {
-                int offset = isLandscape
-                        ? launchedWhileDockingOffset
-                        : (int) (offscreenYOffset * 0.9f);
-                mTmpTransform.rect.offset(0, offset);
-                mTmpTransform.alpha = 0f;
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
-            }
-        }
-    }
-
-    /**
-     * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places
-     * depending on how Recents was triggered.
-     */
-    public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) {
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        Resources res = mStackView.getResources();
-        Resources appRes = mStackView.getContext().getApplicationContext().getResources();
-
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-        TaskStack stack = mStackView.getStack();
-        Task launchTargetTask = stack.getLaunchTarget();
-
-        // Break early if there are no tasks
-        if (stack.getTaskCount() == 0) {
-            return;
-        }
-
-        final boolean isLowRamDevice = LegacyRecentsImpl.getConfiguration().isLowRamDevice;
-        int taskViewEnterFromAppDuration = res.getInteger(
-                R.integer.recents_task_enter_from_app_duration);
-        int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
-                R.integer.recents_task_enter_from_affiliated_app_duration);
-        int dockGestureAnimDuration = appRes.getInteger(
-                R.integer.long_press_dock_anim_duration);
-
-        // Since low ram devices have an animation when entering app -> recents, do not allow
-        // toggle until the animation is complete
-        if (launchState.launchedFromApp && !launchState.launchedViaDockGesture && isLowRamDevice) {
-            postAnimationTrigger.addLastDecrementRunnable(() -> EventBus.getDefault()
-                .send(new SetWaitingForTransitionStartEvent(false)));
-        }
-
-        // Create enter animations for each of the views from front to back
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            int taskIndexFromFront = taskViewCount - i - 1;
-            int taskIndexFromBack = i;
-            final TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            // Get the current transform for the task, which will be updated to the final transform
-            // to animate to depending on how recents was invoked
-            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
-
-            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
-                if (task.isLaunchTarget) {
-                    tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
-                            taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
-                            postAnimationTrigger);
-                }
-
-            } else if (launchState.launchedFromHome) {
-                // Animate the tasks up, but offset the animations to be relative to the front-most
-                // task animation
-                final float startOffsetFraction = (float) (Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS,
-                        taskIndexFromFront) * mEnterAndExitFromHomeTranslationOffset) /
-                        ENTER_FROM_HOME_TRANSLATION_DURATION;
-                AnimationProps taskAnimation = new AnimationProps()
-                        .setInterpolator(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
-                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
-                if (isLowRamDevice) {
-                    taskAnimation.setInterpolator(AnimationProps.BOUNDS,
-                            Interpolators.FAST_OUT_SLOW_IN)
-                            .setDuration(AnimationProps.BOUNDS, 150)
-                            .setDuration(AnimationProps.ALPHA, 150);
-                } else {
-                    taskAnimation.setStartDelay(AnimationProps.ALPHA,
-                                Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
-                                        FRAME_OFFSET_MS)
-                            .setInterpolator(AnimationProps.BOUNDS,
-                                new RecentsEntrancePathInterpolator(0f, 0f, 0.2f, 1f,
-                                        startOffsetFraction))
-                            .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION)
-                            .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION);
-                }
-                postAnimationTrigger.increment();
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-                if (i == taskViewCount - 1) {
-                    tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled);
-                }
-            } else if (launchState.launchedViaDockGesture) {
-                // Animate the tasks up - add some delay to match the divider animation
-                AnimationProps taskAnimation = new AnimationProps()
-                        .setDuration(AnimationProps.BOUNDS, dockGestureAnimDuration +
-                                (taskIndexFromBack * DOUBLE_FRAME_OFFSET_MS))
-                        .setInterpolator(AnimationProps.BOUNDS,
-                                ENTER_WHILE_DOCKING_INTERPOLATOR)
-                        .setStartDelay(AnimationProps.BOUNDS, 48)
-                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
-                postAnimationTrigger.increment();
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-            }
-        }
-    }
-
-    /**
-     * Starts an in-app animation to hide all the task views so that we can transition back home.
-     */
-    public void startExitToHomeAnimation(boolean animated,
-            ReferenceCountedTrigger postAnimationTrigger) {
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStack stack = mStackView.getStack();
-
-        // Break early if there are no tasks
-        if (stack.getTaskCount() == 0) {
-            return;
-        }
-
-        int offscreenYOffset = stackLayout.mStackRect.height();
-
-        // Create the animations for each of the tasks
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            int taskIndexFromFront = taskViewCount - i - 1;
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (mStackView.isIgnoredTask(task)) {
-                continue;
-            }
-
-            // Animate the tasks down
-            AnimationProps taskAnimation;
-            if (animated) {
-                int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) *
-                        mEnterAndExitFromHomeTranslationOffset;
-                taskAnimation = new AnimationProps()
-                        .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION)
-                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
-                if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                    taskAnimation.setInterpolator(AnimationProps.BOUNDS,
-                            Interpolators.FAST_OUT_SLOW_IN);
-                } else {
-                    taskAnimation.setStartDelay(AnimationProps.BOUNDS, delay)
-                            .setInterpolator(AnimationProps.BOUNDS,
-                                    EXIT_TO_HOME_TRANSLATION_INTERPOLATOR);
-                }
-                postAnimationTrigger.increment();
-            } else {
-                taskAnimation = AnimationProps.IMMEDIATE;
-            }
-
-            mTmpTransform.fillIn(tv);
-            if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                taskAnimation.setInterpolator(AnimationProps.ALPHA,
-                                EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
-                        .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_TRANSLATION_DURATION);
-                mTmpTransform.rect.offset(0, stackLayout.mTaskStackLowRamLayoutAlgorithm
-                        .getTaskRect().height() / 4);
-                mTmpTransform.alpha = 0f;
-            } else {
-                mTmpTransform.rect.offset(0, offscreenYOffset);
-            }
-            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-        }
-    }
-
-    /**
-     * Starts the animation for the launching task view, hiding any tasks that might occlude the
-     * window transition for the launching task.
-     */
-    public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        Resources res = mStackView.getResources();
-
-        int taskViewExitToAppDuration = res.getInteger(
-                R.integer.recents_task_exit_to_app_duration);
-        int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
-                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
-
-        Task launchingTask = launchingTaskView.getTask();
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (tv == launchingTaskView) {
-                tv.setClipViewInStack(false);
-                postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                    @Override
-                    public void run() {
-                        tv.setClipViewInStack(true);
-                    }
-                });
-                tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
-                        screenPinningRequested, postAnimationTrigger);
-            }
-        }
-    }
-
-    /**
-     * Starts the delete animation for the specified {@link TaskView}.
-     */
-    public void startDeleteTaskAnimation(final TaskView deleteTaskView, boolean gridLayout,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        if (gridLayout) {
-            startTaskGridDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
-        } else {
-            startTaskStackDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
-        }
-    }
-
-    /**
-     * Starts the delete animation for all the {@link TaskView}s.
-     */
-    public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, boolean gridLayout,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        if (gridLayout) {
-            for (int i = 0; i < taskViews.size(); i++) {
-                startTaskGridDeleteTaskAnimation(taskViews.get(i), postAnimationTrigger);
-            }
-        } else {
-            startTaskStackDeleteAllTasksAnimation(taskViews, postAnimationTrigger);
-        }
-    }
-
-    /**
-     * Starts the animation to focus the next {@link TaskView} when paging through recents.
-     *
-     * @return whether or not this will trigger a scroll in the stack
-     */
-    public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
-            boolean requestViewFocus) {
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-        TaskStack stack = mStackView.getStack();
-
-        final float curScroll = stackScroller.getStackScroll();
-        final float newScroll = stackScroller.getBoundedStackScroll(
-                stackLayout.getStackScrollForTask(newFocusedTask));
-        boolean willScrollToFront = newScroll > curScroll;
-        boolean willScroll = Float.compare(newScroll, curScroll) != 0;
-
-        // Get the current set of task transforms
-        int taskViewCount = mStackView.getTaskViews().size();
-        ArrayList<Task> stackTasks = stack.getTasks();
-        mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
-
-        // Pick up the newly visible views after the scroll
-        mStackView.bindVisibleTaskViews(newScroll);
-
-        // Update the internal state
-        stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
-        stackScroller.setStackScroll(newScroll, null /* animation */);
-        mStackView.cancelDeferredTaskViewLayoutAnimation();
-
-        // Get the final set of task transforms
-        mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
-                true /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
-
-        // Focus the task view
-        TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
-        if (newFocusedTaskView == null) {
-            // Log the error if we have no task view, and skip the animation
-            Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount +
-                    " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll +
-                    " postscroll: " + newScroll);
-            return false;
-        }
-        newFocusedTaskView.setFocusedState(true, requestViewFocus);
-
-        // Setup the end listener to return all the hidden views to the view pool after the
-        // focus animation
-        ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
-        postAnimTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                mStackView.bindVisibleTaskViews(newScroll);
-            }
-        });
-
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        taskViewCount = taskViews.size();
-        int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (mStackView.isIgnoredTask(task)) {
-                continue;
-            }
-
-            int taskIndex = stackTasks.indexOf(task);
-            TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
-            TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
-
-            // Update the task to the initial state (for the newly picked up tasks)
-            mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
-
-            int duration;
-            Interpolator interpolator;
-            if (willScrollToFront) {
-                duration = calculateStaggeredAnimDuration(i);
-                interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
-            } else {
-                if (i < newFocusTaskViewIndex) {
-                    duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
-                    interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
-                } else if (i > newFocusTaskViewIndex) {
-                    duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
-                    interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
-                } else {
-                    duration = 200;
-                    interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
-                }
-            }
-
-            AnimationProps anim = new AnimationProps()
-                    .setDuration(AnimationProps.BOUNDS, duration)
-                    .setInterpolator(AnimationProps.BOUNDS, interpolator)
-                    .setListener(postAnimTrigger.decrementOnAnimationEnd());
-            postAnimTrigger.increment();
-            mStackView.updateTaskViewToTransform(tv, toTransform, anim);
-        }
-        return willScroll;
-    }
-
-    /**
-     * Starts the animation to go to the initial stack layout with a task focused.  In addition, the
-     * previous task will be animated in after the scroll completes.
-     */
-    public void startNewStackScrollAnimation(TaskStack newStack,
-            ReferenceCountedTrigger animationTrigger) {
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mStackView.getScroller();
-
-        // Get the current set of task transforms
-        ArrayList<Task> stackTasks = newStack.getTasks();
-        mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
-
-        // Update the stack
-        mStackView.setTasks(newStack, false /* allowNotifyStackChanges */);
-        mStackView.updateLayoutAlgorithm(false /* boundScroll */);
-
-        // Pick up the newly visible views after the scroll
-        final float newScroll = stackLayout.mInitialScrollP;
-        mStackView.bindVisibleTaskViews(newScroll);
-
-        // Update the internal state
-        stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
-        stackLayout.setTaskOverridesForInitialState(newStack, true /* ignoreScrollToFront */);
-        stackScroller.setStackScroll(newScroll);
-        mStackView.cancelDeferredTaskViewLayoutAnimation();
-
-        // Get the final set of task transforms
-        mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
-                false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
-
-        // Hide the front most task view until the scroll is complete
-        Task frontMostTask = newStack.getFrontMostTask();
-        final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
-        final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
-                stackTasks.indexOf(frontMostTask));
-        if (frontMostTaskView != null) {
-            mStackView.updateTaskViewToTransform(frontMostTaskView,
-                    stackLayout.getFrontOfStackTransform(), AnimationProps.IMMEDIATE);
-        }
-
-        // Setup the end listener to return all the hidden views to the view pool after the
-        // focus animation
-        animationTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                mStackView.bindVisibleTaskViews(newScroll);
-
-                // Now, animate in the front-most task
-                if (frontMostTaskView != null) {
-                    mStackView.updateTaskViewToTransform(frontMostTaskView, frontMostTransform,
-                            new AnimationProps(75, 250, FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR));
-                }
-            }
-        });
-
-        List<TaskView> taskViews = mStackView.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (mStackView.isIgnoredTask(task)) {
-                continue;
-            }
-            if (task == frontMostTask && frontMostTaskView != null) {
-                continue;
-            }
-
-            int taskIndex = stackTasks.indexOf(task);
-            TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
-            TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
-
-            // Update the task to the initial state (for the newly picked up tasks)
-            mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
-
-            int duration = calculateStaggeredAnimDuration(i);
-            Interpolator interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
-
-            AnimationProps anim = new AnimationProps()
-                    .setDuration(AnimationProps.BOUNDS, duration)
-                    .setInterpolator(AnimationProps.BOUNDS, interpolator)
-                    .setListener(animationTrigger.decrementOnAnimationEnd());
-            animationTrigger.increment();
-            mStackView.updateTaskViewToTransform(tv, toTransform, anim);
-        }
-    }
-
-    /**
-     * Caclulates a staggered duration for {@link #startScrollToFocusedTaskAnimation} and
-     * {@link #startNewStackScrollAnimation}.
-     */
-    private int calculateStaggeredAnimDuration(int i) {
-        return Math.max(100, 100 + ((i - 1) * 50));
-    }
-
-    private void startTaskGridDeleteTaskAnimation(final TaskView deleteTaskView,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        postAnimationTrigger.increment();
-        postAnimationTrigger.addLastDecrementRunnable(() -> {
-            mStackView.getTouchHandler().onChildDismissed(deleteTaskView);
-        });
-        deleteTaskView.animate().setDuration(300).scaleX(0.9f).scaleY(0.9f).alpha(0).setListener(
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        postAnimationTrigger.decrement();
-                    }}).start();
-    }
-
-    private void startTaskStackDeleteTaskAnimation(final TaskView deleteTaskView,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        TaskStackViewTouchHandler touchHandler = mStackView.getTouchHandler();
-        touchHandler.onBeginManualDrag(deleteTaskView);
-
-        postAnimationTrigger.increment();
-        postAnimationTrigger.addLastDecrementRunnable(() -> {
-            touchHandler.onChildDismissed(deleteTaskView);
-        });
-
-        final float dismissSize = touchHandler.getScaledDismissSize();
-        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
-        animator.setDuration(400);
-        animator.addUpdateListener((animation) -> {
-            float progress = (Float) animation.getAnimatedValue();
-            deleteTaskView.setTranslationX(progress * dismissSize);
-            touchHandler.updateSwipeProgress(deleteTaskView, true, progress);
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                postAnimationTrigger.decrement();
-            }
-        });
-        animator.start();
-    }
-
-    private void startTaskStackDeleteAllTasksAnimation(final List<TaskView> taskViews,
-            final ReferenceCountedTrigger postAnimationTrigger) {
-        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
-
-        int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.getTaskRect().left;
-
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            int taskIndexFromFront = taskViewCount - i - 1;
-            int startDelay = taskIndexFromFront * DOUBLE_FRAME_OFFSET_MS;
-
-            // Disabling clipping with the stack while the view is animating away
-            tv.setClipViewInStack(false);
-
-            // Compose the new animation and transform and star the animation
-            AnimationProps taskAnimation = new AnimationProps(startDelay,
-                    DISMISS_ALL_TASKS_DURATION, DISMISS_ALL_TRANSLATION_INTERPOLATOR,
-                    new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            postAnimationTrigger.decrement();
-
-                            // Re-enable clipping with the stack (we will reuse this view)
-                            tv.setClipViewInStack(true);
-                        }
-                    });
-            postAnimationTrigger.increment();
-
-            mTmpTransform.fillIn(tv);
-            mTmpTransform.rect.offset(offscreenXOffset, 0);
-            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
deleted file mode 100644
index 58a3f12..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ /dev/null
@@ -1,1283 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.ViewDebug;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.FreePathInterpolator;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Used to describe a visible range that can be normalized to [0, 1].
- */
-class Range {
-    final float relativeMin;
-    final float relativeMax;
-    float origin;
-    float min;
-    float max;
-
-    public Range(float relMin, float relMax) {
-        min = relativeMin = relMin;
-        max = relativeMax = relMax;
-    }
-
-    /**
-     * Offsets this range to a given absolute position.
-     */
-    public void offset(float x) {
-        this.origin = x;
-        min = x + relativeMin;
-        max = x + relativeMax;
-    }
-
-    /**
-     * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
-     *
-     * @param x is an absolute value in the same domain as origin
-     */
-    public float getNormalizedX(float x) {
-        if (x < origin) {
-            return 0.5f + 0.5f * (x - origin) / -relativeMin;
-        } else {
-            return 0.5f + 0.5f * (x - origin) / relativeMax;
-        }
-    }
-
-    /**
-     * Given a normalized {@param x} value in this range, projected onto the full range to get an
-     * absolute value about the given {@param origin}.
-     */
-    public float getAbsoluteX(float normX) {
-        if (normX < 0.5f) {
-            return (normX - 0.5f) / 0.5f * -relativeMin;
-        } else {
-            return (normX - 0.5f) / 0.5f * relativeMax;
-        }
-    }
-
-    /**
-     * Returns whether a value at an absolute x would be within range.
-     */
-    public boolean isInRange(float absX) {
-        return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
-    }
-}
-
-/**
- * The layout logic for a TaskStackView.  This layout needs to be able to calculate the stack layout
- * without an activity-specific context only with the information passed in.  This layout can have
- * two states focused and unfocused, and in the focused state, there is a task that is displayed
- * more prominently in the stack.
- */
-public class TaskStackLayoutAlgorithm {
-
-    private static final String TAG = "TaskStackLayoutAlgorithm";
-
-    // The distribution of view bounds alpha
-    // XXX: This is a hack because you can currently set the max alpha to be > 1f
-    public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
-    public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
-
-    // The medium/maximum dim on the tasks
-    private static final float MED_DIM = 0.15f;
-    private static final float MAX_DIM = 0.25f;
-
-    // The various focus states
-    public static final int STATE_FOCUSED = 1;
-    public static final int STATE_UNFOCUSED = 0;
-
-    // The side that an offset is anchored
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({FROM_TOP, FROM_BOTTOM})
-    public @interface AnchorSide {}
-    private static final int FROM_TOP = 0;
-    private static final int FROM_BOTTOM = 1;
-
-    // The extent that we care about when calculating fractions
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({WIDTH, HEIGHT})
-    public @interface Extent {}
-    private static final int WIDTH = 0;
-    private static final int HEIGHT = 1;
-
-    public interface TaskStackLayoutAlgorithmCallbacks {
-        void onFocusStateChanged(int prevFocusState, int curFocusState);
-    }
-
-    /**
-     * @return True if we should use the grid layout.
-     */
-    boolean useGridLayout() {
-        return LegacyRecentsImpl.getConfiguration().isGridEnabled;
-    }
-
-    // A report of the visibility state of the stack
-    public static class VisibilityReport {
-        public int numVisibleTasks;
-        public int numVisibleThumbnails;
-
-        public VisibilityReport(int tasks, int thumbnails) {
-            numVisibleTasks = tasks;
-            numVisibleThumbnails = thumbnails;
-        }
-    }
-
-    Context mContext;
-    private TaskStackLayoutAlgorithmCallbacks mCb;
-
-    // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mTaskRect = new Rect();
-    // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mStackRect = new Rect();
-    // This is the current system insets
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mSystemInsets = new Rect();
-
-    // The visible ranges when the stack is focused and unfocused
-    private Range mUnfocusedRange;
-    private Range mFocusedRange;
-
-    // This is the bounds of the stack action above the stack rect
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mStackActionButtonRect = new Rect();
-    // The base top margin for the stack from the system insets
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mBaseTopMargin;
-    // The base side margin for the stack from the system insets
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mBaseSideMargin;
-    // The base bottom margin for the stack from the system insets
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mBaseBottomMargin;
-    private int mMinMargin;
-
-    // The initial offset that the focused task is from the top
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mInitialTopOffset;
-    private int mBaseInitialTopOffset;
-    // The initial offset that the launch-from task is from the bottom
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mInitialBottomOffset;
-    private int mBaseInitialBottomOffset;
-
-    // The height between the top margin and the top of the focused task
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mFocusedTopPeekHeight;
-    // The height between the bottom margin and the top of task in front of the focused task
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mFocusedBottomPeekHeight;
-
-    // The offset from the bottom of the stack to the bottom of the bounds when the stack is
-    // scrolled to the front
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mStackBottomOffset;
-
-    /** The height, in pixels, of each task view's title bar. */
-    private int mTitleBarHeight;
-
-    // The paths defining the motion of the tasks when the stack is focused and unfocused
-    private Path mUnfocusedCurve;
-    private Path mFocusedCurve;
-    private FreePathInterpolator mUnfocusedCurveInterpolator;
-    private FreePathInterpolator mFocusedCurveInterpolator;
-
-    // The paths defining the distribution of the dim to apply to tasks in the stack when focused
-    // and unfocused
-    private Path mUnfocusedDimCurve;
-    private Path mFocusedDimCurve;
-    private FreePathInterpolator mUnfocusedDimCurveInterpolator;
-    private FreePathInterpolator mFocusedDimCurveInterpolator;
-
-    // The state of the stack focus (0..1), which controls the transition of the stack from the
-    // focused to non-focused state
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mFocusState;
-
-    // The smallest scroll progress, at this value, the back most task will be visible
-    @ViewDebug.ExportedProperty(category="recents")
-    float mMinScrollP;
-    // The largest scroll progress, at this value, the front most task will be visible above the
-    // navigation bar
-    @ViewDebug.ExportedProperty(category="recents")
-    float mMaxScrollP;
-    // The initial progress that the scroller is set when you first enter recents
-    @ViewDebug.ExportedProperty(category="recents")
-    float mInitialScrollP;
-    // The task progress for the front-most task in the stack
-    @ViewDebug.ExportedProperty(category="recents")
-    float mFrontMostTaskP;
-
-    // The last computed task counts
-    @ViewDebug.ExportedProperty(category="recents")
-    int mNumStackTasks;
-
-    // The min/max z translations
-    @ViewDebug.ExportedProperty(category="recents")
-    int mMinTranslationZ;
-    @ViewDebug.ExportedProperty(category="recents")
-    public int mMaxTranslationZ;
-
-    // Optimization, allows for quick lookup of task -> index
-    private SparseIntArray mTaskIndexMap = new SparseIntArray();
-    private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
-
-    TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
-    TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
-
-    // The transform to place TaskViews at the front and back of the stack respectively
-    TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
-    TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
-
-    public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
-        mContext = context;
-        mCb = cb;
-        mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
-        mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
-        reloadOnConfigurationChange(context);
-    }
-
-    /**
-     * Reloads the layout for the current configuration.
-     */
-    public void reloadOnConfigurationChange(Context context) {
-        Resources res = context.getResources();
-        mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
-                res.getFloat(R.integer.recents_layout_focused_range_max));
-        mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
-                res.getFloat(R.integer.recents_layout_unfocused_range_max));
-        mFocusState = getInitialFocusState();
-        mFocusedTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_top_peek_size);
-        mFocusedBottomPeekHeight =
-                res.getDimensionPixelSize(R.dimen.recents_layout_bottom_peek_size);
-        mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_min);
-        mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_max);
-        mBaseInitialTopOffset = getDimensionForDevice(context,
-                R.dimen.recents_layout_initial_top_offset_phone_port,
-                R.dimen.recents_layout_initial_top_offset_phone_land,
-                R.dimen.recents_layout_initial_top_offset_tablet,
-                R.dimen.recents_layout_initial_top_offset_tablet,
-                R.dimen.recents_layout_initial_top_offset_tablet,
-                R.dimen.recents_layout_initial_top_offset_tablet,
-                R.dimen.recents_layout_initial_top_offset_tablet);
-        mBaseInitialBottomOffset = getDimensionForDevice(context,
-                R.dimen.recents_layout_initial_bottom_offset_phone_port,
-                R.dimen.recents_layout_initial_bottom_offset_phone_land,
-                R.dimen.recents_layout_initial_bottom_offset_tablet,
-                R.dimen.recents_layout_initial_bottom_offset_tablet,
-                R.dimen.recents_layout_initial_bottom_offset_tablet,
-                R.dimen.recents_layout_initial_bottom_offset_tablet,
-                R.dimen.recents_layout_initial_bottom_offset_tablet);
-        mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
-        mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
-        mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
-        mBaseTopMargin = getDimensionForDevice(context,
-                R.dimen.recents_layout_top_margin_phone,
-                R.dimen.recents_layout_top_margin_tablet,
-                R.dimen.recents_layout_top_margin_tablet_xlarge,
-                R.dimen.recents_layout_top_margin_tablet);
-        mBaseSideMargin = getDimensionForDevice(context,
-                R.dimen.recents_layout_side_margin_phone,
-                R.dimen.recents_layout_side_margin_tablet,
-                R.dimen.recents_layout_side_margin_tablet_xlarge,
-                R.dimen.recents_layout_side_margin_tablet);
-        mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
-        mTitleBarHeight = getDimensionForDevice(mContext,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_grid_task_view_header_height);
-    }
-
-    /**
-     * Resets this layout when the stack view is reset.
-     */
-    public void reset() {
-        mTaskIndexOverrideMap.clear();
-        setFocusState(getInitialFocusState());
-    }
-
-    /**
-     * Sets the system insets.
-     */
-    public boolean setSystemInsets(Rect systemInsets) {
-        boolean changed = !mSystemInsets.equals(systemInsets);
-        mSystemInsets.set(systemInsets);
-        mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets);
-        mTaskStackLowRamLayoutAlgorithm.setSystemInsets(systemInsets);
-        return changed;
-    }
-
-    /**
-     * Sets the focused state.
-     */
-    public void setFocusState(int focusState) {
-        int prevFocusState = mFocusState;
-        mFocusState = focusState;
-        updateFrontBackTransforms();
-        if (mCb != null) {
-            mCb.onFocusStateChanged(prevFocusState, focusState);
-        }
-    }
-
-    /**
-     * Gets the focused state.
-     */
-    public int getFocusState() {
-        return mFocusState;
-    }
-
-    /**
-     * Computes the stack and task rects.  The given task stack bounds already has the top/right
-     * insets and left/right padding already applied.
-     */
-    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
-        Rect lastStackRect = new Rect(mStackRect);
-
-        int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
-        int bottomMargin = getScaleForExtent(windowRect, displayRect, mBaseBottomMargin, mMinMargin,
-                HEIGHT);
-        mInitialTopOffset = getScaleForExtent(windowRect, displayRect, mBaseInitialTopOffset,
-                mMinMargin, HEIGHT);
-        mInitialBottomOffset = mBaseInitialBottomOffset;
-
-        // Compute the stack bounds
-        mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
-        mStackRect.set(taskStackBounds);
-        mStackRect.top += topMargin;
-
-        // The stack action button will take the full un-padded header space above the stack
-        mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
-                mStackRect.right, mStackRect.top + mFocusedTopPeekHeight);
-
-        // Anchor the task rect top aligned to the stack rect
-        int height = mStackRect.height() - mInitialTopOffset - mStackBottomOffset;
-        mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.right, mStackRect.top + height);
-
-        if (mTaskRect.width() <= 0 || mTaskRect.height() <= 0) {
-            // Logging for b/36654830
-            Log.e(TAG, "Invalid task rect: taskRect=" + mTaskRect + " stackRect=" + mStackRect
-                    + " displayRect=" + displayRect + " windowRect=" + windowRect
-                    + " taskStackBounds=" + taskStackBounds);
-        }
-
-        // Short circuit here if the stack rects haven't changed so we don't do all the work below
-        if (!lastStackRect.equals(mStackRect)) {
-            // Reinitialize the focused and unfocused curves
-            mUnfocusedCurve = constructUnfocusedCurve();
-            mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
-            mFocusedCurve = constructFocusedCurve();
-            mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
-            mUnfocusedDimCurve = constructUnfocusedDimCurve();
-            mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
-            mFocusedDimCurve = constructFocusedDimCurve();
-            mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
-
-            updateFrontBackTransforms();
-        }
-
-        // Initialize the grid layout
-        mTaskGridLayoutAlgorithm.initialize(windowRect);
-        mTaskStackLowRamLayoutAlgorithm.initialize(windowRect);
-    }
-
-    /**
-     * Computes the minimum and maximum scroll progress values and the progress values for each task
-     * in the stack.
-     */
-    public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
-            RecentsActivityLaunchState launchState, float lastScrollPPercent) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-
-        // Clear the progress map
-        mTaskIndexMap.clear();
-
-        // Return early if we have no tasks
-        ArrayList<Task> tasks = stack.getTasks();
-        if (tasks.isEmpty()) {
-            mFrontMostTaskP = 0;
-            mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
-            mNumStackTasks = 0;
-            return;
-        }
-
-        // Filter the set of stack tasks
-        ArrayList<Task> stackTasks = new ArrayList<>();
-        for (int i = 0; i < tasks.size(); i++) {
-            Task task = tasks.get(i);
-            if (ignoreTasksSet.contains(task.key)) {
-                continue;
-            }
-            stackTasks.add(task);
-        }
-        mNumStackTasks = stackTasks.size();
-
-        // Put each of the tasks in the progress map at a fixed index (does not need to actually
-        // map to a scroll position, just by index)
-        int taskCount = stackTasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = stackTasks.get(i);
-            mTaskIndexMap.put(task.key.id, i);
-        }
-
-        // Calculate the min/max/initial scroll
-        Task launchTask = stack.getLaunchTarget();
-        int launchTaskIndex = launchTask != null
-                ? stack.indexOfTask(launchTask)
-                : mNumStackTasks - 1;
-        if (getInitialFocusState() == STATE_FOCUSED) {
-            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
-            float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM);
-            mFocusedRange.offset(0f);
-            mMinScrollP = 0;
-            mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
-                    Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
-            if (launchState.launchedFromHome || launchState.launchedFromPipApp
-                    || launchState.launchedWithNextPipApp) {
-                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
-            } else {
-                mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
-            }
-        } else if (mNumStackTasks == 1) {
-            // If there is one stack task, ignore the min/max/initial scroll positions
-            mMinScrollP = 0;
-            mMaxScrollP = 0;
-            mInitialScrollP = 0;
-        } else {
-            // Set the max scroll to be the point where the front most task is visible with the
-            // stack bottom offset
-            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
-            float maxBottomNormX = getNormalizedXFromUnfocusedY(maxBottomOffset, FROM_BOTTOM);
-            mUnfocusedRange.offset(0f);
-            mMinScrollP = LegacyRecentsImpl.getConfiguration().isLowRamDevice
-                    ? mTaskStackLowRamLayoutAlgorithm.getMinScrollP()
-                    : 0;
-            mMaxScrollP = LegacyRecentsImpl.getConfiguration().isLowRamDevice
-                    ? mTaskStackLowRamLayoutAlgorithm.getMaxScrollP(taskCount)
-                    : Math.max(mMinScrollP, (mNumStackTasks - 1) -
-                    Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
-            boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
-                    || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
-
-            if (launchState.launchedWithAltTab) {
-                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
-            } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
-                mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
-            } else if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
-                        scrollToFront);
-            } else if (scrollToFront) {
-                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
-            } else {
-                // We are overriding the initial two task positions, so set the initial scroll
-                // position to match the second task (aka focused task) position
-                float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
-                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
-                        - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
-            }
-        }
-    }
-
-    /**
-     * Creates task overrides to ensure the initial stack layout if necessary.
-     */
-    public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-
-        mTaskIndexOverrideMap.clear();
-
-        boolean scrollToFront = launchState.launchedFromHome ||
-                launchState.launchedFromPipApp ||
-                launchState.launchedWithNextPipApp ||
-                launchState.launchedViaDockGesture;
-        if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
-            if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
-                // Set the initial scroll to the predefined state (which differs from the stack)
-                float [] initialNormX = null;
-                float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
-                        mInitialBottomOffset, FROM_BOTTOM);
-                float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
-                        mTaskRect.height() - mMinMargin, FROM_TOP);
-                if (mNumStackTasks <= 2) {
-                    // For small stacks, position the tasks so that they are top aligned to under
-                    // the action button, but ensure that it is at least a certain offset from the
-                    // bottom of the stack
-                    initialNormX = new float[] {
-                            Math.min(maxBottomTaskNormX, minBottomTaskNormX),
-                            getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
-                    };
-                } else {
-                    initialNormX = new float[] {
-                            minBottomTaskNormX,
-                            getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
-                    };
-                }
-
-                mUnfocusedRange.offset(0f);
-                List<Task> tasks = stack.getTasks();
-                int taskCount = tasks.size();
-                for (int i = taskCount - 1; i >= 0; i--) {
-                    int indexFromFront = taskCount - i - 1;
-                    if (indexFromFront >= initialNormX.length) {
-                        break;
-                    }
-                    float newTaskProgress = mInitialScrollP +
-                            mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
-                    mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds and override task progress for the given task when transitioning from focused to
-     * unfocused state.
-     */
-    public void addUnfocusedTaskOverride(Task task, float stackScroll) {
-        if (mFocusState != STATE_UNFOCUSED) {
-            mFocusedRange.offset(stackScroll);
-            mUnfocusedRange.offset(stackScroll);
-            float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
-            float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
-            float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
-            float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
-            if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
-                mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
-            }
-        }
-    }
-
-    /**
-     * Adds and override task progress for the given task when transitioning from focused to
-     * unfocused state.
-     */
-    public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
-        mFocusedRange.offset(stackScroll);
-        mUnfocusedRange.offset(stackScroll);
-
-        Task task = taskView.getTask();
-        int top = taskView.getTop() - mTaskRect.top;
-        float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
-        float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
-        float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
-        if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
-            mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
-        }
-    }
-
-    public void clearUnfocusedTaskOverrides() {
-        mTaskIndexOverrideMap.clear();
-    }
-
-    /**
-     * Updates this stack when a scroll happens.
-     *
-     */
-    public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
-            float lastStackScroll) {
-        if (targetStackScroll == lastStackScroll || LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return targetStackScroll;
-        }
-
-        float deltaScroll = targetStackScroll - lastStackScroll;
-        float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
-        float newScroll = targetStackScroll;
-        mUnfocusedRange.offset(targetStackScroll);
-        for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
-            int taskId = mTaskIndexOverrideMap.keyAt(i);
-            float x = mTaskIndexMap.get(taskId);
-            float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
-            float newOverrideX = overrideX + deltaScroll;
-            if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
-                // Remove the override once we reach the original task index
-                mTaskIndexOverrideMap.removeAt(i);
-            } else if ((overrideX >= x && deltaScroll <= 0f) ||
-                    (overrideX <= x && deltaScroll >= 0f)) {
-                // Scrolling from override x towards x, then lock the task in place
-                mTaskIndexOverrideMap.put(taskId, newOverrideX);
-            } else {
-                // Scrolling override x away from x, we should still move the scroll towards x
-                newScroll = lastStackScroll;
-                newOverrideX = overrideX - deltaTargetScroll;
-                if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
-                    mTaskIndexOverrideMap.removeAt(i);
-                } else{
-                    mTaskIndexOverrideMap.put(taskId, newOverrideX);
-                }
-            }
-        }
-        return newScroll;
-    }
-
-    private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
-        boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
-                mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
-        return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
-                (overrideX <= x && x <= newOverrideX);
-    }
-
-    /**
-     * Returns the default focus state.
-     */
-    public int getInitialFocusState() {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-        RecentsDebugFlags debugFlags = LegacyRecentsImpl.getDebugFlags();
-        if (launchState.launchedWithAltTab) {
-            return STATE_FOCUSED;
-        } else {
-            return STATE_UNFOCUSED;
-        }
-    }
-
-    public Rect getStackActionButtonRect() {
-        return useGridLayout()
-                ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
-    }
-
-    /**
-     * Returns the TaskViewTransform that would put the task just off the back of the stack.
-     */
-    public TaskViewTransform getBackOfStackTransform() {
-        return mBackOfStackTransform;
-    }
-
-    /**
-     * Returns the TaskViewTransform that would put the task just off the front of the stack.
-     */
-    public TaskViewTransform getFrontOfStackTransform() {
-        return mFrontOfStackTransform;
-    }
-
-    /**
-     * Returns whether this stack layout has been initialized.
-     */
-    public boolean isInitialized() {
-        return !mStackRect.isEmpty();
-    }
-
-    /**
-     * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
-     * stack scroll.  Requires that update() is called first.
-     */
-    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
-        if (useGridLayout()) {
-            return mTaskGridLayoutAlgorithm.computeStackVisibilityReport(tasks);
-        }
-
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return mTaskStackLowRamLayoutAlgorithm.computeStackVisibilityReport(tasks);
-        }
-
-        // Ensure minimum visibility count
-        if (tasks.size() <= 1) {
-            return new VisibilityReport(1, 1);
-        }
-
-        // Otherwise, walk backwards in the stack and count the number of tasks and visible
-        // thumbnails and add that to the total task count
-        TaskViewTransform tmpTransform = new TaskViewTransform();
-        Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
-        currentRange.offset(mInitialScrollP);
-        int taskBarHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_task_view_header_height);
-        int numVisibleTasks = 0;
-        int numVisibleThumbnails = 0;
-        float prevScreenY = Integer.MAX_VALUE;
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-
-            // Skip invisible
-            float taskProgress = getStackScrollForTask(task);
-            if (!currentRange.isInRange(taskProgress)) {
-                continue;
-            }
-
-            getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
-                    tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
-            float screenY = tmpTransform.rect.top;
-            boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
-            if (hasVisibleThumbnail) {
-                numVisibleThumbnails++;
-                numVisibleTasks++;
-                prevScreenY = screenY;
-            } else {
-                // Once we hit the next front most task that does not have a visible thumbnail,
-                // walk through remaining visible set
-                for (int j = i; j >= 0; j--) {
-                    taskProgress = getStackScrollForTask(tasks.get(j));
-                    if (!currentRange.isInRange(taskProgress)) {
-                        break;
-                    }
-                    numVisibleTasks++;
-                }
-                break;
-            }
-        }
-        return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
-    }
-
-    /**
-     * Returns the transform for the given task.  This transform is relative to the mTaskRect, which
-     * is what the view is measured and laid out with.
-     */
-    public TaskViewTransform getStackTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
-        return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, false /* ignoreTaskOverrides */);
-    }
-
-    public TaskViewTransform getStackTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform,
-            boolean ignoreTaskOverrides) {
-        return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, ignoreTaskOverrides);
-    }
-
-    public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
-            boolean ignoreTaskOverrides) {
-        if (useGridLayout()) {
-            int taskIndex = mTaskIndexMap.get(task.key.id);
-            int taskCount = mTaskIndexMap.size();
-            mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
-            return transformOut;
-        } else if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            if (task == null) {
-                transformOut.reset();
-                return transformOut;
-            }
-            int taskIndex = mTaskIndexMap.get(task.key.id);
-            mTaskStackLowRamLayoutAlgorithm.getTransform(taskIndex, stackScroll, transformOut,
-                    mNumStackTasks, this);
-            return transformOut;
-        } else {
-            // Return early if we have an invalid index
-            int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
-            if (task == null || nonOverrideTaskProgress == -1) {
-                transformOut.reset();
-                return transformOut;
-            }
-            float taskProgress = ignoreTaskOverrides
-                    ? nonOverrideTaskProgress
-                    : getStackScrollForTask(task);
-
-            getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
-                    transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
-            return transformOut;
-        }
-    }
-
-    /**
-     * Like {@link #getStackTransform}, but in screen coordinates
-     */
-    public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform,
-            Rect windowOverrideRect) {
-        TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
-                transformOut, frontTransform, true /* forceUpdate */,
-                false /* ignoreTaskOverrides */);
-        return transformToScreenCoordinates(transform, windowOverrideRect);
-    }
-
-    /**
-     * Transforms the given {@param transformOut} to the screen coordinates, overriding the current
-     * window rectangle with {@param windowOverrideRect} if non-null.
-     */
-    TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
-            Rect windowOverrideRect) {
-        Rect windowRect = windowOverrideRect != null
-                ? windowOverrideRect
-                : LegacyRecentsImpl.getSystemServices().getWindowRect();
-        transformOut.rect.offset(windowRect.left, windowRect.top);
-        if (useGridLayout()) {
-            // Draw the thumbnail a little lower to perfectly coincide with the view we are
-            // transitioning to, where the header bar has already been drawn.
-            transformOut.rect.offset(0, mTitleBarHeight);
-        }
-        return transformOut;
-    }
-
-    /**
-     * Update/get the transform.
-     *
-     * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
-     *                             into account the special single-task case.  This is only used
-     *                             internally to ensure that we can calculate the transform for any
-     *                             position in the stack.
-     */
-    public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
-            float stackScroll, int focusState, TaskViewTransform transformOut,
-            TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-
-        // Ensure that the task is in range
-        mUnfocusedRange.offset(stackScroll);
-        mFocusedRange.offset(stackScroll);
-        boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
-        boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
-
-        // Skip if the task is not visible
-        if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
-            transformOut.reset();
-            return;
-        }
-
-        // Map the absolute task progress to the normalized x at the stack scroll.  We use this to
-        // calculate positions along the curve.
-        mUnfocusedRange.offset(stackScroll);
-        mFocusedRange.offset(stackScroll);
-        float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
-        float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
-
-        // Map the absolute task progress to the normalized x at the bounded stack scroll.  We use
-        // this to calculate bounded properties, like translationZ and outline alpha.
-        float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
-        mUnfocusedRange.offset(boundedStackScroll);
-        mFocusedRange.offset(boundedStackScroll);
-        float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
-        float boundedScrollUnfocusedNonOverrideRangeX =
-                mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
-
-        // Map the absolute task progress to the normalized x at the upper bounded stack scroll.
-        // We use this to calculate the dim, which is bounded only on one end.
-        float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
-        mUnfocusedRange.offset(lowerBoundedStackScroll);
-        mFocusedRange.offset(lowerBoundedStackScroll);
-        float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
-        float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
-
-        int x = (mStackRect.width() - mTaskRect.width()) / 2;
-        int y;
-        float z;
-        float dimAlpha;
-        float viewOutlineAlpha;
-        if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
-            // When there is exactly one task, then decouple the task from the stack and just move
-            // in screen space
-            float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
-            int centerYOffset = (mStackRect.top - mTaskRect.top) +
-                    (mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
-            y = centerYOffset + getYForDeltaP(tmpP, 0);
-            z = mMaxTranslationZ;
-            dimAlpha = 0f;
-            viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
-                    (OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
-
-        } else {
-            // Otherwise, update the task to the stack layout
-            int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
-                    unfocusedRangeX)) * mStackRect.height());
-            int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
-                    focusedRangeX)) * mStackRect.height());
-            float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
-                    lowerBoundedUnfocusedRangeX);
-            float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
-                    lowerBoundedFocusedRangeX);
-
-            // Special case, because we override the initial task positions differently for small
-            // stacks, we clamp the dim to 0 in the initial position, and then only modulate the
-            // dim when the task is scrolled back towards the top of the screen
-            if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
-                if (boundedScrollUnfocusedRangeX >= 0.5f) {
-                    unfocusedDim = 0f;
-                } else {
-                    float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
-                    unfocusedDim -= offset;
-                    unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
-                }
-            }
-            y = (mStackRect.top - mTaskRect.top) +
-                    (int) com.android.systemui.recents.utilities.Utilities
-                            .mapRange(focusState, unfocusedY, focusedY);
-            z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
-                    mMinTranslationZ, mMaxTranslationZ);
-            dimAlpha = com.android.systemui.recents.utilities.Utilities
-                    .mapRange(focusState, unfocusedDim, focusedDim);
-            viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
-                    OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
-        }
-
-        // Fill out the transform
-        transformOut.scale = 1f;
-        transformOut.alpha = 1f;
-        transformOut.translationZ = z;
-        transformOut.dimAlpha = dimAlpha;
-        transformOut.viewOutlineAlpha = viewOutlineAlpha;
-        transformOut.rect.set(mTaskRect);
-        transformOut.rect.offset(x, y);
-        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-        transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
-                (frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
-    }
-
-    /**
-     * Returns the untransformed task view bounds.
-     */
-    public Rect getUntransformedTaskViewBounds() {
-        return new Rect(mTaskRect);
-    }
-
-    /**
-     * Returns the scroll progress to scroll to such that the top of the task is at the top of the
-     * stack.
-     */
-    float getStackScrollForTask(Task t) {
-        Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice || overrideP == null) {
-            return (float) mTaskIndexMap.get(t.key.id, 0);
-        }
-        return overrideP;
-    }
-
-    /**
-     * Returns the original scroll progress to scroll to such that the top of the task is at the top
-     * of the stack.
-     */
-    float getStackScrollForTaskIgnoreOverrides(Task t) {
-        return (float) mTaskIndexMap.get(t.key.id, 0);
-    }
-
-    /**
-     * Returns the scroll progress to scroll to such that the top of the task at the initial top
-     * offset (which is at the task's brightest point).
-     */
-    float getStackScrollForTaskAtInitialOffset(Task t) {
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-            return mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
-                    launchState.launchedFromHome || launchState.launchedFromPipApp
-                            || launchState.launchedWithNextPipApp);
-        }
-        float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
-        mUnfocusedRange.offset(0f);
-        return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
-                mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
-    }
-
-    /**
-     * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
-     * length of the curve.  We know the curve is mostly flat, so we just map the length of the
-     * screen along the arc-length proportionally (1/arclength).
-     */
-    public float getDeltaPForY(int downY, int y) {
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return mTaskStackLowRamLayoutAlgorithm.scrollToPercentage(downY - y);
-        }
-        float deltaP = (float) (y - downY) / mStackRect.height() *
-                mUnfocusedCurveInterpolator.getArcLength();
-        return -deltaP;
-    }
-
-    /**
-     * This is the inverse of {@link #getDeltaPForY}.  Given a movement along the arc length
-     * of the curve, map back to the screen y.
-     */
-    public int getYForDeltaP(float downScrollP, float p) {
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return mTaskStackLowRamLayoutAlgorithm.percentageToScroll(downScrollP - p);
-        }
-        int y = (int) ((p - downScrollP) * mStackRect.height() *
-                (1f / mUnfocusedCurveInterpolator.getArcLength()));
-        return -y;
-    }
-
-    /**
-     * Returns the task stack bounds in the current orientation.  This rect takes into account the
-     * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
-     * the top/bottom padding or insets.
-     */
-    public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset,
-            int rightInset, Rect taskStackBounds) {
-        taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset,
-                windowRect.right - rightInset, windowRect.bottom);
-
-        // Ensure that the new width is at most the smaller display edge size
-        int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
-                WIDTH);
-        int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
-        if (Utilities.getAppConfiguration(mContext).orientation
-                == Configuration.ORIENTATION_LANDSCAPE) {
-            // If we are in landscape, calculate the width of the stack in portrait and ensure that
-            // we are not larger than that size
-            Rect portraitDisplayRect = new Rect(0, 0,
-                    Math.min(displayRect.width(), displayRect.height()),
-                    Math.max(displayRect.width(), displayRect.height()));
-            int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
-                    mBaseSideMargin, mMinMargin, WIDTH);
-            targetStackWidth = Math.min(targetStackWidth,
-                    portraitDisplayRect.width() - 2 * portraitSideMargin);
-        }
-        taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
-    }
-
-    /**
-     * Retrieves resources that are constant regardless of the current configuration of the device.
-     */
-    public static int getDimensionForDevice(Context ctx, int phoneResId,
-            int tabletResId, int xlargeTabletResId, int gridLayoutResId) {
-        return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
-                xlargeTabletResId, xlargeTabletResId, gridLayoutResId);
-    }
-
-    /**
-     * Retrieves resources that are constant regardless of the current configuration of the device.
-     */
-    public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
-            int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
-            int xlargeTabletLandResId, int gridLayoutResId) {
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        Resources res = ctx.getResources();
-        boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
-                Configuration.ORIENTATION_LANDSCAPE;
-        if (config.isGridEnabled) {
-            return res.getDimensionPixelSize(gridLayoutResId);
-        } else if (config.isXLargeScreen) {
-            return res.getDimensionPixelSize(isLandscape
-                    ? xlargeTabletLandResId
-                    : xlargeTabletPortResId);
-        } else if (config.isLargeScreen) {
-            return res.getDimensionPixelSize(isLandscape
-                    ? tabletLandResId
-                    : tabletPortResId);
-        } else {
-            return res.getDimensionPixelSize(isLandscape
-                    ? phoneLandResId
-                    : phonePortResId);
-        }
-    }
-
-    /**
-     * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
-     * stack height).
-     */
-    private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
-        float offset = (fromSide == FROM_TOP)
-                ? mStackRect.height() - y
-                : y;
-        float offsetPct = offset / mStackRect.height();
-        return mUnfocusedCurveInterpolator.getX(offsetPct);
-    }
-
-    /**
-     * Returns the normalized x on the focused curve given an absolute Y position (relative to the
-     * stack height).
-     */
-    private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
-        float offset = (fromSide == FROM_TOP)
-                ? mStackRect.height() - y
-                : y;
-        float offsetPct = offset / mStackRect.height();
-        return mFocusedCurveInterpolator.getX(offsetPct);
-    }
-
-    /**
-     * Creates a new path for the focused curve.
-     */
-    private Path constructFocusedCurve() {
-        // Initialize the focused curve. This curve is a piecewise curve composed of several
-        // linear pieces that goes from (0,1) through (0.5, peek height offset),
-        // (0.5, bottom task offsets), and (1,0).
-        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
-        float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
-                mStackRect.height();
-        float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
-                mMinMargin) / mStackRect.height();
-        Path p = new Path();
-        p.moveTo(0f, 1f);
-        p.lineTo(0.5f, 1f - topPeekHeightPct);
-        p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
-                bottomPeekHeightPct));
-        p.lineTo(1f, 0f);
-        return p;
-    }
-
-    /**
-     * Creates a new path for the unfocused curve.
-     */
-    private Path constructUnfocusedCurve() {
-        // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
-        // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0).  This
-        // ensures that we match the range, at which 0.5 represents the stack scroll at the current
-        // task progress.  Because the height offset can change depending on a resource, we compute
-        // the control point of the second bezier such that between it and a first known point,
-        // there is a tangent at (0.5, peek height offset).
-        float cpoint1X = 0.4f;
-        float cpoint1Y = 0.975f;
-        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
-        float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
-        float b = 1f - slope * cpoint1X;
-        float cpoint2X = 0.65f;
-        float cpoint2Y = slope * cpoint2X + b;
-        Path p = new Path();
-        p.moveTo(0f, 1f);
-        p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
-        p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
-        return p;
-    }
-
-    /**
-     * Creates a new path for the focused dim curve.
-     */
-    private Path constructFocusedDimCurve() {
-        Path p = new Path();
-        // The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
-        // task), then goes back to max dim at the next task
-        p.moveTo(0f, MAX_DIM);
-        p.lineTo(0.5f, 0f);
-        p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
-        p.lineTo(1f, MAX_DIM);
-        return p;
-    }
-
-    /**
-     * Creates a new path for the unfocused dim curve.
-     */
-    private Path constructUnfocusedDimCurve() {
-        float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
-        float cpoint2X = focusX + (1f - focusX) / 2;
-        Path p = new Path();
-        // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
-        // task), then goes back to max dim towards the front of the stack
-        p.moveTo(0f, MAX_DIM);
-        p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
-        p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
-        return p;
-    }
-
-    /**
-     * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
-     * {@param other} rect in the {@param extent} side.
-     */
-    private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
-                                  @Extent int extent) {
-        if (extent == WIDTH) {
-            float scale = Utilities.clamp01((float) instance.width() / other.width());
-            return Math.max(minValue, (int) (scale * value));
-        } else if (extent == HEIGHT) {
-            float scale = Utilities.clamp01((float) instance.height() / other.height());
-            return Math.max(minValue, (int) (scale * value));
-        }
-        return value;
-    }
-
-    /**
-     * Updates the current transforms that would put a TaskView at the front and back of the stack.
-     */
-    private void updateFrontBackTransforms() {
-        // Return early if we have not yet initialized
-        if (mStackRect.isEmpty()) {
-            return;
-        }
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            mTaskStackLowRamLayoutAlgorithm.getBackOfStackTransform(mBackOfStackTransform, this);
-            mTaskStackLowRamLayoutAlgorithm.getFrontOfStackTransform(mFrontOfStackTransform, this);
-            return;
-        }
-
-        float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
-                mFocusedRange.relativeMin);
-        float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
-                mFocusedRange.relativeMax);
-        getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
-                true /* ignoreSingleTaskCase */, true /* forceUpdate */);
-        getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
-                true /* ignoreSingleTaskCase */, true /* forceUpdate */);
-        mBackOfStackTransform.visible = true;
-        mFrontOfStackTransform.visible = true;
-    }
-
-    /**
-     * Returns the proper task rectangle according to the current grid state.
-     */
-    public Rect getTaskRect() {
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return mTaskStackLowRamLayoutAlgorithm.getTaskRect();
-        }
-        return useGridLayout() ? mTaskGridLayoutAlgorithm.getTaskGridRect() : mTaskRect;
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print(TAG);
-        writer.write(" numStackTasks="); writer.print(mNumStackTasks);
-        writer.println();
-
-        writer.print(innerPrefix);
-        writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
-        writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
-        writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
-        writer.print(" actionButton="); writer.print(
-                Utilities.dumpRect(mStackActionButtonRect));
-        writer.println();
-
-        writer.print(innerPrefix);
-        writer.print("minScroll="); writer.print(mMinScrollP);
-        writer.print(" maxScroll="); writer.print(mMaxScrollP);
-        writer.print(" initialScroll="); writer.print(mInitialScrollP);
-        writer.println();
-
-        writer.print(innerPrefix);
-        writer.print("focusState="); writer.print(mFocusState);
-        writer.println();
-
-        if (mTaskIndexOverrideMap.size() > 0) {
-            for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
-                int taskId = mTaskIndexOverrideMap.keyAt(i);
-                float x = mTaskIndexMap.get(taskId);
-                float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
-
-                writer.print(innerPrefix);
-                writer.print("taskId= "); writer.print(taskId);
-                writer.print(" x= "); writer.print(x);
-                writer.print(" overrideX= "); writer.print(overrideX);
-                writer.println();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java
deleted file mode 100644
index b89218c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java
+++ /dev/null
@@ -1,2291 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.MutableBoolean;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.ScrollView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
-import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
-import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
-import com.android.systemui.recents.events.component.ActivityPinnedEvent;
-import com.android.systemui.recents.events.component.ExpandPipEvent;
-import com.android.systemui.recents.events.component.HidePipMenuEvent;
-import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
-import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
-import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
-import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
-import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
-import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UserInteractionEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
-import com.android.systemui.recents.misc.DozeTrigger;
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.recents.views.grid.GridTaskView;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-
-/* The visual representation of a task stack view */
-public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
-        TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
-        TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks,
-        ViewPool.ViewPoolConsumer<TaskView, Task> {
-
-    private static final String TAG = "TaskStackView";
-
-    // The thresholds at which to show/hide the stack action button.
-    private static final float SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f;
-    private static final float HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f;
-
-    public static final int DEFAULT_SYNC_STACK_DURATION = 200;
-    public static final int SLOW_SYNC_STACK_DURATION = 250;
-    private static final int DRAG_SCALE_DURATION = 175;
-    static final float DRAG_SCALE_FACTOR = 1.05f;
-
-    private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216;
-    private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32;
-
-    // The actions to perform when resetting to initial state,
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({INITIAL_STATE_UPDATE_NONE, INITIAL_STATE_UPDATE_ALL, INITIAL_STATE_UPDATE_LAYOUT_ONLY})
-    public @interface InitialStateAction {}
-    /** Do not update the stack and layout to the initial state. */
-    private static final int INITIAL_STATE_UPDATE_NONE = 0;
-    /** Update both the stack and layout to the initial state. */
-    private static final int INITIAL_STATE_UPDATE_ALL = 1;
-    /** Update only the layout to the initial state. */
-    private static final int INITIAL_STATE_UPDATE_LAYOUT_ONLY = 2;
-
-    private LayoutInflater mInflater;
-    private TaskStack mStack = new TaskStack();
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="layout_")
-    TaskStackLayoutAlgorithm mLayoutAlgorithm;
-    // The stable layout algorithm is only used to calculate the task rect with the stable bounds
-    private TaskStackLayoutAlgorithm mStableLayoutAlgorithm;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="scroller_")
-    private TaskStackViewScroller mStackScroller;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
-    private TaskStackViewTouchHandler mTouchHandler;
-    private TaskStackAnimationHelper mAnimationHelper;
-    private ViewPool<TaskView, Task> mViewPool;
-
-    private ArrayList<TaskView> mTaskViews = new ArrayList<>();
-    private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
-    private ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
-    private AnimationProps mDeferredTaskViewLayoutAnimation = null;
-
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_")
-    private DozeTrigger mUIDozeTrigger;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_")
-    private Task mFocusedTask;
-
-    private int mTaskCornerRadiusPx;
-    private int mDividerSize;
-    private int mStartTimerIndicatorDuration;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mTaskViewsClipDirty = true;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mEnterAnimationComplete = false;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mStackReloaded = false;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mFinishedLayoutAfterStackReload = false;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mLaunchNextAfterFirstMeasure = false;
-    @ViewDebug.ExportedProperty(category="recents")
-    @InitialStateAction
-    private int mInitialState = INITIAL_STATE_UPDATE_ALL;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mInMeasureLayout = false;
-    @ViewDebug.ExportedProperty(category="recents")
-    boolean mTouchExplorationEnabled;
-    @ViewDebug.ExportedProperty(category="recents")
-    boolean mScreenPinningEnabled;
-
-    // The stable stack bounds are the full bounds that we were measured with from RecentsView
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mStableStackBounds = new Rect();
-    // The current stack bounds are dynamic and may change as the user drags and drops
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mStackBounds = new Rect();
-    // The current window bounds at the point we were measured
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mStableWindowRect = new Rect();
-    // The current window bounds are dynamic and may change as the user drags and drops
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mWindowRect = new Rect();
-    // The current display bounds
-    @ViewDebug.ExportedProperty(category="recents")
-    private Rect mDisplayRect = new Rect();
-    // The current display orientation
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
-
-    private Rect mTmpRect = new Rect();
-    private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
-    private List<TaskView> mTmpTaskViews = new ArrayList<>();
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-    private int[] mTmpIntPair = new int[2];
-    private boolean mResetToInitialStateWhenResized;
-    private int mLastWidth;
-    private int mLastHeight;
-    private boolean mStackActionButtonVisible;
-
-    // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
-    private float mLastScrollPPercent = -1;
-
-    // We keep track of the task view focused by user interaction and draw a frame around it in the
-    // grid layout.
-    private TaskViewFocusFrame mTaskViewFocusFrame;
-
-    private Task mPrefetchingTask;
-    private final float mFastFlingVelocity;
-
-    // A convenience update listener to request updating clipping of tasks
-    private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    if (!mTaskViewsClipDirty) {
-                        mTaskViewsClipDirty = true;
-                        invalidate();
-                    }
-                }
-            };
-
-    private DropTarget mStackDropTarget = new DropTarget() {
-        @Override
-        public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-                boolean isCurrentTarget) {
-            // This drop target has a fixed bounds and should be checked last, so just fall through
-            // if it is the current target
-            if (!isCurrentTarget) {
-                return mLayoutAlgorithm.mStackRect.contains(x, y);
-            }
-            return false;
-        }
-    };
-
-    public TaskStackView(Context context) {
-        super(context);
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        Resources res = context.getResources();
-
-        // Set the stack first
-        mStack.setCallbacks(this);
-        mViewPool = new ViewPool<>(context, this);
-        mInflater = LayoutInflater.from(context);
-        mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
-        mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
-        mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
-        mTouchHandler = new TaskStackViewTouchHandler(
-                context, this, mStackScroller, Dependency.get(FalsingManager.class));
-        mAnimationHelper = new TaskStackAnimationHelper(context, this);
-        mTaskCornerRadiusPx = LegacyRecentsImpl.getConfiguration().isGridEnabled ?
-                res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
-                res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
-        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
-        mDividerSize = ssp.getDockedDividerSize(context);
-        mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
-        mDisplayRect = ssp.getDisplayRect();
-        mStackActionButtonVisible = false;
-
-        // Create a frame to draw around the focused task view
-        if (LegacyRecentsImpl.getConfiguration().isGridEnabled) {
-            mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this,
-                mLayoutAlgorithm.mTaskGridLayoutAlgorithm);
-            addView(mTaskViewFocusFrame);
-            getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame);
-        }
-
-        int taskBarDismissDozeDelaySeconds = getResources().getInteger(
-                R.integer.recents_task_bar_dismiss_delay_seconds);
-        mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
-            @Override
-            public void run() {
-                // Show the task bar dismiss buttons
-                List<TaskView> taskViews = getTaskViews();
-                int taskViewCount = taskViews.size();
-                for (int i = 0; i < taskViewCount; i++) {
-                    TaskView tv = taskViews.get(i);
-                    tv.startNoUserInteractionAnimation();
-                }
-            }
-        });
-        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-        super.onAttachedToWindow();
-        readSystemFlags();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        EventBus.getDefault().unregister(this);
-    }
-
-    /**
-     * Called from RecentsActivity when it is relaunched.
-     */
-    void onReload(boolean isResumingFromVisible) {
-        if (!isResumingFromVisible) {
-            // Reset the focused task
-            resetFocusedTask(getFocusedTask());
-        }
-
-        // Reset the state of each of the task views
-        List<TaskView> taskViews = new ArrayList<>();
-        taskViews.addAll(getTaskViews());
-        taskViews.addAll(mViewPool.getViews());
-        for (int i = taskViews.size() - 1; i >= 0; i--) {
-            taskViews.get(i).onReload(isResumingFromVisible);
-        }
-
-        // Reset the stack state
-        readSystemFlags();
-        mTaskViewsClipDirty = true;
-        mUIDozeTrigger.stopDozing();
-        if (!isResumingFromVisible) {
-            mStackScroller.reset();
-            mStableLayoutAlgorithm.reset();
-            mLayoutAlgorithm.reset();
-            mLastScrollPPercent = -1;
-        }
-
-        // Since we always animate to the same place in (the initial state), always reset the stack
-        // to the initial state when resuming
-        mStackReloaded = true;
-        mFinishedLayoutAfterStackReload = false;
-        mLaunchNextAfterFirstMeasure = false;
-        mInitialState = INITIAL_STATE_UPDATE_ALL;
-        requestLayout();
-    }
-
-    /**
-     * Sets the stack tasks of this TaskStackView from the given TaskStack.
-     */
-    public void setTasks(TaskStack stack, boolean allowNotifyStackChanges) {
-        boolean isInitialized = mLayoutAlgorithm.isInitialized();
-
-        // Only notify if we are already initialized, otherwise, everything will pick up all the
-        // new and old tasks when we next layout
-        mStack.setTasks(stack, allowNotifyStackChanges && isInitialized);
-    }
-
-    /** Returns the task stack. */
-    public TaskStack getStack() {
-        return mStack;
-    }
-
-    /**
-     * Updates this TaskStackView to the initial state.
-     */
-    public void updateToInitialState() {
-        mStackScroller.setStackScrollToInitialState();
-        mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */);
-    }
-
-    /** Updates the list of task views */
-    void updateTaskViewsList() {
-        mTaskViews.clear();
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View v = getChildAt(i);
-            if (v instanceof TaskView) {
-                mTaskViews.add((TaskView) v);
-            }
-        }
-    }
-
-    /** Gets the list of task views */
-    List<TaskView> getTaskViews() {
-        return mTaskViews;
-    }
-
-    /**
-     * Returns the front most task view.
-     */
-    private TaskView getFrontMostTaskView() {
-        List<TaskView> taskViews = getTaskViews();
-        if (taskViews.isEmpty()) {
-            return null;
-        }
-        return taskViews.get(taskViews.size() - 1);
-    }
-
-    /**
-     * Finds the child view given a specific {@param task}.
-     */
-    public TaskView getChildViewForTask(Task t) {
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            if (tv.getTask() == t) {
-                return tv;
-            }
-        }
-        return null;
-    }
-
-    /** Returns the stack algorithm for this task stack. */
-    public TaskStackLayoutAlgorithm getStackAlgorithm() {
-        return mLayoutAlgorithm;
-    }
-
-    /** Returns the grid algorithm for this task stack. */
-    public TaskGridLayoutAlgorithm getGridAlgorithm() {
-        return mLayoutAlgorithm.mTaskGridLayoutAlgorithm;
-    }
-
-    /**
-     * Returns the touch handler for this task stack.
-     */
-    public TaskStackViewTouchHandler getTouchHandler() {
-        return mTouchHandler;
-    }
-
-    /**
-     * Adds a task to the ignored set.
-     */
-    void addIgnoreTask(Task task) {
-        mIgnoreTasks.add(task.key);
-    }
-
-    /**
-     * Removes a task from the ignored set.
-     */
-    void removeIgnoreTask(Task task) {
-        mIgnoreTasks.remove(task.key);
-    }
-
-    /**
-     * Returns whether the specified {@param task} is ignored.
-     */
-    boolean isIgnoredTask(Task task) {
-        return mIgnoreTasks.contains(task.key);
-    }
-
-    /**
-     * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
-     * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
-     * visible range includes all tasks at the target stack scroll. This is useful for ensure that
-     * all views necessary for a transition or animation will be visible at the start.
-     *
-     * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
-     *                       match the size of {@param tasks}
-     * @param tasks The set of tasks for which to generate transforms
-     * @param curStackScroll The current stack scroll
-     * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
-     *                          The range of the union of the visible views at the current and
-     *                          target stack scrolls will be returned.
-     * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
-     *                       Transforms will still be calculated for the ignore tasks.
-     * @return the front and back most visible task indices (there may be non visible tasks in
-     *         between this range)
-     */
-    int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
-            ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
-            ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) {
-        int taskCount = tasks.size();
-        int[] visibleTaskRange = mTmpIntPair;
-        visibleTaskRange[0] = -1;
-        visibleTaskRange[1] = -1;
-        boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
-
-        // We can reuse the task transforms where possible to reduce object allocation
-        matchTaskListSize(tasks, taskTransforms);
-
-        // Update the stack transforms
-        TaskViewTransform frontTransform = null;
-        TaskViewTransform frontTransformAtTarget = null;
-        TaskViewTransform transform = null;
-        TaskViewTransform transformAtTarget = null;
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-
-            // Calculate the current and (if necessary) the target transform for the task
-            transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
-                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
-            if (useTargetStackScroll && !transform.visible) {
-                // If we have a target stack scroll and the task is not currently visible, then we
-                // just update the transform at the new scroll
-                // TODO: Optimize this
-                transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll,
-                    new TaskViewTransform(), frontTransformAtTarget);
-                if (transformAtTarget.visible) {
-                    transform.copyFrom(transformAtTarget);
-                }
-            }
-
-            // For ignore tasks, only calculate the stack transform and skip the calculation of the
-            // visible stack indices
-            if (ignoreTasksSet.contains(task.key)) {
-                continue;
-            }
-
-            frontTransform = transform;
-            frontTransformAtTarget = transformAtTarget;
-            if (transform.visible) {
-                if (visibleTaskRange[0] < 0) {
-                    visibleTaskRange[0] = i;
-                }
-                visibleTaskRange[1] = i;
-            }
-        }
-        return visibleTaskRange;
-    }
-
-    /**
-     * Binds the visible {@link TaskView}s at the given target scroll.
-     */
-    void bindVisibleTaskViews(float targetStackScroll) {
-        bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */);
-    }
-
-    /**
-     * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
-     * current {@link TaskStack}. This call does not continue on to update their position to the
-     * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
-     * be added/removed from the view hierarchy and placed in the correct Z order and initial
-     * position (if not currently on screen).
-     *
-     * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
-     *                          includes those visible at the current stack scroll, and all at the
-     *                          target stack scroll.
-     * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for
-     *                            tasks at their non-overridden task progress
-     */
-    void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
-        // Get all the task transforms
-        ArrayList<Task> tasks = mStack.getTasks();
-        int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
-                mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
-                ignoreTaskOverrides);
-
-        // Return all the invisible children to the pool
-        mTmpTaskViewMap.clear();
-        List<TaskView> taskViews = getTaskViews();
-        int lastFocusedTaskIndex = -1;
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            // Skip ignored tasks
-            if (mIgnoreTasks.contains(task.key)) {
-                continue;
-            }
-
-            // It is possible for the set of lingering TaskViews to differ from the stack if the
-            // stack was updated before the relayout.  If the task view is no longer in the stack,
-            // then just return it back to the view pool.
-            int taskIndex = mStack.indexOfTask(task);
-            TaskViewTransform transform = null;
-            if (taskIndex != -1) {
-                transform = mCurrentTaskTransforms.get(taskIndex);
-            }
-
-            if (transform != null && transform.visible) {
-                mTmpTaskViewMap.put(task.key, tv);
-            } else {
-                if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
-                    lastFocusedTaskIndex = taskIndex;
-                    resetFocusedTask(task);
-                }
-                mViewPool.returnViewToPool(tv);
-            }
-        }
-
-        // Pick up all the newly visible children
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-            TaskViewTransform transform = mCurrentTaskTransforms.get(i);
-
-            // Skip ignored tasks
-            if (mIgnoreTasks.contains(task.key)) {
-                continue;
-            }
-
-            // Skip the invisible stack tasks
-            if (!transform.visible) {
-                continue;
-            }
-
-            TaskView tv = mTmpTaskViewMap.get(task.key);
-            if (tv == null) {
-                tv = mViewPool.pickUpViewFromPool(task, task);
-                if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
-                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
-                            AnimationProps.IMMEDIATE);
-                } else {
-                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
-                            AnimationProps.IMMEDIATE);
-                }
-            } else {
-                // Reattach it in the right z order
-                final int taskIndex = mStack.indexOfTask(task);
-                final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
-                if (insertIndex != getTaskViews().indexOf(tv)){
-                    detachViewFromParent(tv);
-                    attachViewToParent(tv, insertIndex, tv.getLayoutParams());
-                    updateTaskViewsList();
-                }
-            }
-        }
-
-        updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
-
-        // Update the focus if the previous focused task was returned to the view pool
-        if (lastFocusedTaskIndex != -1) {
-            int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
-                    ? visibleTaskRange[1]
-                    : visibleTaskRange[0];
-            setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */,
-                    true /* requestViewFocus */);
-            TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
-            if (focusedTaskView != null) {
-                focusedTaskView.requestAccessibilityFocus();
-            }
-        }
-    }
-
-    /**
-     * @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean)
-     */
-    public void relayoutTaskViews(AnimationProps animation) {
-        relayoutTaskViews(animation, null /* animationOverrides */,
-                false /* ignoreTaskOverrides */);
-    }
-
-    /**
-     * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
-     * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
-     * animations that are current running on those task views, and will ensure that the children
-     * {@link TaskView}s will match the set of visible tasks in the stack.  If a {@link Task} has
-     * an animation provided in {@param animationOverrides}, that will be used instead.
-     */
-    private void relayoutTaskViews(AnimationProps animation,
-            ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides) {
-        // If we had a deferred animation, cancel that
-        cancelDeferredTaskViewLayoutAnimation();
-
-        // Synchronize the current set of TaskViews
-        bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTaskOverrides);
-
-        // Animate them to their final transforms with the given animation
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (mIgnoreTasks.contains(task.key)) {
-                continue;
-            }
-
-            int taskIndex = mStack.indexOfTask(task);
-            TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
-            if (animationOverrides != null && animationOverrides.containsKey(task)) {
-                animation = animationOverrides.get(task);
-            }
-
-            updateTaskViewToTransform(tv, transform, animation);
-        }
-    }
-
-    /**
-     * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
-     */
-    void relayoutTaskViewsOnNextFrame(AnimationProps animation) {
-        mDeferredTaskViewLayoutAnimation = animation;
-        invalidate();
-    }
-
-    /**
-     * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
-     * given set of {@link AnimationProps} properties.
-     */
-    public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
-            AnimationProps animation) {
-        if (taskView.isAnimatingTo(transform)) {
-            return;
-        }
-        taskView.cancelTransformAnimation();
-        taskView.updateViewPropertiesToTaskTransform(transform, animation,
-                mRequestUpdateClippingListener);
-    }
-
-    /**
-     * Returns the current task transforms of all tasks, falling back to the stack layout if there
-     * is no {@link TaskView} for the task.
-     */
-    public void getCurrentTaskTransforms(ArrayList<Task> tasks,
-            ArrayList<TaskViewTransform> transformsOut) {
-        matchTaskListSize(tasks, transformsOut);
-        int focusState = mLayoutAlgorithm.getFocusState();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-            TaskViewTransform transform = transformsOut.get(i);
-            TaskView tv = getChildViewForTask(task);
-            if (tv != null) {
-                transform.fillIn(tv);
-            } else {
-                mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
-                        focusState, transform, null, true /* forceUpdate */,
-                        false /* ignoreTaskOverrides */);
-            }
-            transform.visible = true;
-        }
-    }
-
-    /**
-     * Returns the task transforms for all the tasks in the stack if the stack was at the given
-     * {@param stackScroll} and {@param focusState}.
-     */
-    public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
-            boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
-        matchTaskListSize(tasks, transformsOut);
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-            TaskViewTransform transform = transformsOut.get(i);
-            mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
-                    true /* forceUpdate */, ignoreTaskOverrides);
-            transform.visible = true;
-        }
-    }
-
-    /**
-     * Cancels the next deferred task view layout.
-     */
-    void cancelDeferredTaskViewLayoutAnimation() {
-        mDeferredTaskViewLayoutAnimation = null;
-    }
-
-    /**
-     * Cancels all {@link TaskView} animations.
-     */
-    void cancelAllTaskViewAnimations() {
-        List<TaskView> taskViews = getTaskViews();
-        for (int i = taskViews.size() - 1; i >= 0; i--) {
-            final TaskView tv = taskViews.get(i);
-            if (!mIgnoreTasks.contains(tv.getTask().key)) {
-                tv.cancelTransformAnimation();
-            }
-        }
-    }
-
-    /**
-     * Updates the clip for each of the task views from back to front.
-     */
-    private void clipTaskViews() {
-        // We never clip task views in grid layout
-        if (LegacyRecentsImpl.getConfiguration().isGridEnabled) {
-            return;
-        }
-
-        // Update the clip on each task child
-        List<TaskView> taskViews = getTaskViews();
-        TaskView tmpTv = null;
-        TaskView prevVisibleTv = null;
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            TaskView frontTv = null;
-            int clipBottom = 0;
-
-            if (isIgnoredTask(tv.getTask())) {
-                // For each of the ignore tasks, update the translationZ of its TaskView to be
-                // between the translationZ of the tasks immediately underneath it
-                if (prevVisibleTv != null) {
-                    tv.setTranslationZ(Math.max(tv.getTranslationZ(),
-                            prevVisibleTv.getTranslationZ() + 0.1f));
-                }
-            }
-
-            if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
-                // Find the next view to clip against
-                for (int j = i + 1; j < taskViewCount; j++) {
-                    tmpTv = taskViews.get(j);
-
-                    if (tmpTv.shouldClipViewInStack()) {
-                        frontTv = tmpTv;
-                        break;
-                    }
-                }
-
-                // Clip against the next view, this is just an approximation since we are
-                // stacked and we can make assumptions about the visibility of the this
-                // task relative to the ones in front of it.
-                if (frontTv != null) {
-                    float taskBottom = tv.getBottom();
-                    float frontTaskTop = frontTv.getTop();
-                    if (frontTaskTop < taskBottom) {
-                        // Map the stack view space coordinate (the rects) to view space
-                        clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx;
-                    }
-                }
-            }
-            tv.getViewBounds().setClipBottom(clipBottom);
-            tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
-            prevVisibleTv = tv;
-        }
-        mTaskViewsClipDirty = false;
-    }
-
-    public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
-        updateLayoutAlgorithm(boundScrollToNewMinMax, LegacyRecentsImpl.getConfiguration().getLaunchState());
-    }
-
-    /**
-     * Updates the layout algorithm min and max virtual scroll bounds.
-     */
-   public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
-           RecentsActivityLaunchState launchState) {
-        // Compute the min and max scroll values
-        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
-
-        if (boundScrollToNewMinMax) {
-            mStackScroller.boundScroll();
-        }
-    }
-
-    /**
-     * Updates the stack layout to its stable places.
-     */
-    private void updateLayoutToStableBounds() {
-        mWindowRect.set(mStableWindowRect);
-        mStackBounds.set(mStableStackBounds);
-        mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
-        updateLayoutAlgorithm(true /* boundScroll */);
-    }
-
-    /** Returns the scroller. */
-    public TaskStackViewScroller getScroller() {
-        return mStackScroller;
-    }
-
-    /**
-     * Sets the focused task to the provided (bounded taskIndex).
-     *
-     * @return whether or not the stack will scroll as a part of this focus change
-     */
-    public boolean setFocusedTask(int taskIndex, boolean scrollToTask,
-            final boolean requestViewFocus) {
-        return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
-    }
-
-    /**
-     * Sets the focused task to the provided (bounded focusTaskIndex).
-     *
-     * @return whether or not the stack will scroll as a part of this focus change
-     */
-    public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
-            boolean requestViewFocus, int timerIndicatorDuration) {
-        // Find the next task to focus
-        int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
-                Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1;
-        final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
-                mStack.getTasks().get(newFocusedTaskIndex) : null;
-
-        // Reset the last focused task state if changed
-        if (mFocusedTask != null) {
-            // Cancel the timer indicator, if applicable
-            if (timerIndicatorDuration > 0) {
-                final TaskView tv = getChildViewForTask(mFocusedTask);
-                if (tv != null) {
-                    tv.getHeaderView().cancelFocusTimerIndicator();
-                }
-            }
-
-            resetFocusedTask(mFocusedTask);
-        }
-
-        boolean willScroll = false;
-        mFocusedTask = newFocusedTask;
-
-        if (newFocusedTask != null) {
-            // Start the timer indicator, if applicable
-            if (timerIndicatorDuration > 0) {
-                final TaskView tv = getChildViewForTask(mFocusedTask);
-                if (tv != null) {
-                    tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration);
-                } else {
-                    // The view is null; set a flag for later
-                    mStartTimerIndicatorDuration = timerIndicatorDuration;
-                }
-            }
-
-            if (scrollToTask) {
-                // Cancel any running enter animations at this point when we scroll or change focus
-                if (!mEnterAnimationComplete) {
-                    cancelAllTaskViewAnimations();
-                }
-
-                mLayoutAlgorithm.clearUnfocusedTaskOverrides();
-                willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
-                        requestViewFocus);
-                if (willScroll) {
-                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-                }
-            } else {
-                // Focus the task view
-                TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
-                if (newFocusedTaskView != null) {
-                    newFocusedTaskView.setFocusedState(true, requestViewFocus);
-                }
-            }
-            // Any time a task view gets the focus, we move the focus frame around it.
-            if (mTaskViewFocusFrame != null) {
-                mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask));
-            }
-        }
-        return willScroll;
-    }
-
-    /**
-     * Sets the focused task relative to the currently focused task.
-     *
-     * @param forward whether to go to the next task in the stack (along the curve) or the previous
-     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
-     *                       if the currently focused task is not a stack task, will set the focus
-     *                       to the first visible stack task
-     * @param animated determines whether to actually draw the highlight along with the change in
-     *                            focus.
-     */
-    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
-        setRelativeFocusedTask(forward, stackTasksOnly, animated, false, 0);
-    }
-
-    /**
-     * Sets the focused task relative to the currently focused task.
-     *
-     * @param forward whether to go to the next task in the stack (along the curve) or the previous
-     * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
-     *                       if the currently focused task is not a stack task, will set the focus
-     *                       to the first visible stack task
-     * @param animated determines whether to actually draw the highlight along with the change in
-     *                            focus.
-     * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
-     *                               happens.
-     * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator
-     */
-    public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
-                                       boolean cancelWindowAnimations, int timerIndicatorDuration) {
-        Task focusedTask = getFocusedTask();
-        int newIndex = mStack.indexOfTask(focusedTask);
-        if (focusedTask != null) {
-            if (stackTasksOnly) {
-                List<Task> tasks =  mStack.getTasks();
-                // Try the next task if it is a stack task
-                int tmpNewIndex = newIndex + (forward ? -1 : 1);
-                if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
-                    newIndex = tmpNewIndex;
-                }
-            } else {
-                // No restrictions, lets just move to the new task (looping forward/backwards if
-                // necessary)
-                int taskCount = mStack.getTaskCount();
-                newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount;
-            }
-        } else {
-            // We don't have a focused task
-            float stackScroll = mStackScroller.getStackScroll();
-            ArrayList<Task> tasks = mStack.getTasks();
-            int taskCount = tasks.size();
-            if (useGridLayout()) {
-                // For the grid layout, we directly set focus to the most recently used task
-                // no matter we're moving forwards or backwards.
-                newIndex = taskCount - 1;
-            } else {
-                // For the grid layout we pick a proper task to focus, according to the current
-                // stack scroll.
-                if (forward) {
-                    // Walk backwards and focus the next task smaller than the current stack scroll
-                    for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
-                        float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
-                        if (Float.compare(taskP, stackScroll) <= 0) {
-                            break;
-                        }
-                    }
-                } else {
-                    // Walk forwards and focus the next task larger than the current stack scroll
-                    for (newIndex = 0; newIndex < taskCount; newIndex++) {
-                        float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
-                        if (Float.compare(taskP, stackScroll) >= 0) {
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-        if (newIndex != -1) {
-            boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */,
-                    true /* requestViewFocus */, timerIndicatorDuration);
-            if (willScroll && cancelWindowAnimations) {
-                // As we iterate to the next/previous task, cancel any current/lagging window
-                // transition animations
-                EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-            }
-        }
-    }
-
-    /**
-     * Resets the focused task.
-     */
-    public void resetFocusedTask(Task task) {
-        if (task != null) {
-            TaskView tv = getChildViewForTask(task);
-            if (tv != null) {
-                tv.setFocusedState(false, false /* requestViewFocus */);
-            }
-        }
-        if (mTaskViewFocusFrame != null) {
-            mTaskViewFocusFrame.moveGridTaskViewFocus(null);
-        }
-        mFocusedTask = null;
-    }
-
-    /**
-     * Returns the focused task.
-     */
-    public Task getFocusedTask() {
-        return mFocusedTask;
-    }
-
-    /**
-     * Returns the accessibility focused task.
-     */
-    Task getAccessibilityFocusedTask() {
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            if (Utilities.isDescendentAccessibilityFocused(tv)) {
-                return tv.getTask();
-            }
-        }
-        TaskView frontTv = getFrontMostTaskView();
-        if (frontTv != null) {
-            return frontTv.getTask();
-        }
-        return null;
-    }
-
-    @Override
-    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
-        super.onInitializeAccessibilityEvent(event);
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        if (taskViewCount > 0) {
-            TaskView backMostTask = taskViews.get(0);
-            TaskView frontMostTask = taskViews.get(taskViewCount - 1);
-            event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
-            event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
-            event.setContentDescription(frontMostTask.getTask().title);
-        }
-        event.setItemCount(mStack.getTaskCount());
-
-        int stackHeight = mLayoutAlgorithm.mStackRect.height();
-        event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight));
-        event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight));
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        if (taskViewCount > 1) {
-            // Find the accessibility focused task
-            Task focusedTask = getAccessibilityFocusedTask();
-            info.setScrollable(true);
-            int focusedTaskIndex = mStack.indexOfTask(focusedTask);
-            if (focusedTaskIndex > 0 || !mStackActionButtonVisible) {
-                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
-            }
-            if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) {
-                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
-            }
-        }
-    }
-
-    @Override
-    public CharSequence getAccessibilityClassName() {
-        return ScrollView.class.getName();
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (super.performAccessibilityAction(action, arguments)) {
-            return true;
-        }
-        Task focusedTask = getAccessibilityFocusedTask();
-        int taskIndex = mStack.indexOfTask(focusedTask);
-        if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
-            switch (action) {
-                case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
-                    setFocusedTask(taskIndex + 1, true /* scrollToTask */, true /* requestViewFocus */,
-                            0);
-                    return true;
-                }
-                case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
-                    setFocusedTask(taskIndex - 1, true /* scrollToTask */, true /* requestViewFocus */,
-                            0);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return mTouchHandler.onInterceptTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return mTouchHandler.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onGenericMotionEvent(MotionEvent ev) {
-        return mTouchHandler.onGenericMotionEvent(ev);
-    }
-
-    @Override
-    public void computeScroll() {
-        if (mStackScroller.computeScroll()) {
-            // Notify accessibility
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-            LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
-                    mStackScroller.getScrollVelocity() > mFastFlingVelocity);
-        }
-        if (mDeferredTaskViewLayoutAnimation != null) {
-            relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
-            mTaskViewsClipDirty = true;
-            mDeferredTaskViewLayoutAnimation = null;
-        }
-        if (mTaskViewsClipDirty) {
-            clipTaskViews();
-        }
-        mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
-            mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
-    }
-
-    /**
-     * Computes the maximum number of visible tasks and thumbnails. Requires that
-     * updateLayoutForStack() is called first.
-     */
-    public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
-        return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
-    }
-
-    /**
-     * Updates the system insets.
-     */
-    public void setSystemInsets(Rect systemInsets) {
-        boolean changed = false;
-        changed |= mStableLayoutAlgorithm.setSystemInsets(systemInsets);
-        changed |= mLayoutAlgorithm.setSystemInsets(systemInsets);
-        if (changed) {
-            requestLayout();
-        }
-    }
-
-    /**
-     * This is called with the full window width and height to allow stack view children to
-     * perform the full screen transition down.
-     */
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mInMeasureLayout = true;
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Update the stable stack bounds, but only update the current stack bounds if the stable
-        // bounds have changed.  This is because we may get spurious measures while dragging where
-        // our current stack bounds reflect the target drop region.
-        mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height),
-                mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left,
-                mLayoutAlgorithm.mSystemInsets.right, mTmpRect);
-        if (!mTmpRect.equals(mStableStackBounds)) {
-            mStableStackBounds.set(mTmpRect);
-            mStackBounds.set(mTmpRect);
-            mStableWindowRect.set(0, 0, width, height);
-            mWindowRect.set(0, 0, width, height);
-        }
-
-        // Compute the rects in the stack algorithm
-        mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
-        updateLayoutAlgorithm(false /* boundScroll */);
-
-        // If this is the first layout, then scroll to the front of the stack, then update the
-        // TaskViews with the stack so that we can lay them out
-        boolean resetToInitialState = (width != mLastWidth || height != mLastHeight)
-                && mResetToInitialStateWhenResized;
-        if (!mFinishedLayoutAfterStackReload || mInitialState != INITIAL_STATE_UPDATE_NONE
-                || resetToInitialState) {
-            if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) {
-                updateToInitialState();
-                mResetToInitialStateWhenResized = false;
-            }
-            if (mFinishedLayoutAfterStackReload) {
-                mInitialState = INITIAL_STATE_UPDATE_NONE;
-            }
-        }
-        // If we got the launch-next event before the first layout pass, then re-send it after the
-        // initial state has been updated
-        if (mLaunchNextAfterFirstMeasure) {
-            mLaunchNextAfterFirstMeasure = false;
-            EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
-        }
-
-        // Rebind all the views, including the ignore ones
-        bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */);
-
-        // Measure each of the TaskViews
-        mTmpTaskViews.clear();
-        mTmpTaskViews.addAll(getTaskViews());
-        mTmpTaskViews.addAll(mViewPool.getViews());
-        int taskViewCount = mTmpTaskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            measureTaskView(mTmpTaskViews.get(i));
-        }
-        if (mTaskViewFocusFrame != null) {
-            mTaskViewFocusFrame.measure();
-        }
-
-        setMeasuredDimension(width, height);
-        mLastWidth = width;
-        mLastHeight = height;
-        mInMeasureLayout = false;
-    }
-
-    /**
-     * Measures a TaskView.
-     */
-    private void measureTaskView(TaskView tv) {
-        Rect padding = new Rect();
-        if (tv.getBackground() != null) {
-            tv.getBackground().getPadding(padding);
-        }
-        mTmpRect.set(mStableLayoutAlgorithm.getTaskRect());
-        mTmpRect.union(mLayoutAlgorithm.getTaskRect());
-        tv.measure(
-                MeasureSpec.makeMeasureSpec(mTmpRect.width() + padding.left + padding.right,
-                        MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(mTmpRect.height() + padding.top + padding.bottom,
-                        MeasureSpec.EXACTLY));
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        // Layout each of the TaskViews
-        mTmpTaskViews.clear();
-        mTmpTaskViews.addAll(getTaskViews());
-        mTmpTaskViews.addAll(mViewPool.getViews());
-        int taskViewCount = mTmpTaskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            layoutTaskView(changed, mTmpTaskViews.get(i));
-        }
-        if (mTaskViewFocusFrame != null) {
-            mTaskViewFocusFrame.layout();
-        }
-
-        if (changed) {
-            if (mStackScroller.isScrollOutOfBounds()) {
-                mStackScroller.boundScroll();
-            }
-        }
-
-        // Relayout all of the task views including the ignored ones
-        relayoutTaskViews(AnimationProps.IMMEDIATE);
-        clipTaskViews();
-
-        if (!mFinishedLayoutAfterStackReload) {
-            // Prepare the task enter animations (this can be called numerous times)
-            mInitialState = INITIAL_STATE_UPDATE_NONE;
-            onFirstLayout();
-
-            if (mStackReloaded) {
-                mFinishedLayoutAfterStackReload = true;
-                tryStartEnterAnimation();
-            }
-        }
-    }
-
-    /**
-     * Lays out a TaskView.
-     */
-    private void layoutTaskView(boolean changed, TaskView tv) {
-        if (changed) {
-            Rect padding = new Rect();
-            if (tv.getBackground() != null) {
-                tv.getBackground().getPadding(padding);
-            }
-            mTmpRect.set(mStableLayoutAlgorithm.getTaskRect());
-            mTmpRect.union(mLayoutAlgorithm.getTaskRect());
-            tv.cancelTransformAnimation();
-            tv.layout(mTmpRect.left - padding.left, mTmpRect.top - padding.top,
-                    mTmpRect.right + padding.right, mTmpRect.bottom + padding.bottom);
-        } else {
-            // If the layout has not changed, then just lay it out again in-place
-            tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
-        }
-    }
-
-    /** Handler for the first layout. */
-    void onFirstLayout() {
-        // Setup the view for the enter animation
-        mAnimationHelper.prepareForEnterAnimation();
-
-        // Set the task focused state without requesting view focus, and leave the focus animations
-        // until after the enter-animation
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-
-        // We set the initial focused task view iff the following conditions are satisfied:
-        // 1. Recents is showing task views in stack layout.
-        // 2. Recents is launched with ALT + TAB.
-        boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab;
-        if (setFocusOnFirstLayout) {
-            int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(),
-                useGridLayout());
-            if (focusedTaskIndex != -1) {
-                setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
-                        false /* requestViewFocus */);
-            }
-        }
-        updateStackActionButtonVisibility();
-    }
-
-    public boolean isTouchPointInView(float x, float y, TaskView tv) {
-        mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
-        mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
-        return mTmpRect.contains((int) x, (int) y);
-    }
-
-    /**
-     * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
-     * calculating the scroll position before and after a layout change.
-     */
-    public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task task = tasks.get(i);
-
-            // Ignore deleting tasks
-            if (isIgnoredTask(task)) {
-                if (i == tasks.size() - 1) {
-                    isFrontMostTask.value = true;
-                }
-                continue;
-            }
-            return task;
-        }
-        return null;
-    }
-
-    /**** TaskStackCallbacks Implementation ****/
-
-    @Override
-    public void onStackTaskAdded(TaskStack stack, Task newTask) {
-        // Update the min/max scroll and animate other task views into their new positions
-        updateLayoutAlgorithm(true /* boundScroll */);
-
-        // Animate all the tasks into place
-        relayoutTaskViews(!mFinishedLayoutAfterStackReload
-                ? AnimationProps.IMMEDIATE
-                : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN));
-    }
-
-    /**
-     * We expect that the {@link TaskView} associated with the removed task is already hidden.
-     */
-    @Override
-    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
-            AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved) {
-        if (mFocusedTask == removedTask) {
-            resetFocusedTask(removedTask);
-        }
-
-        // Remove the view associated with this task, we can't rely on updateTransforms
-        // to work here because the task is no longer in the list
-        TaskView tv = getChildViewForTask(removedTask);
-        if (tv != null) {
-            mViewPool.returnViewToPool(tv);
-        }
-
-        // Remove the task from the ignored set
-        removeIgnoreTask(removedTask);
-
-        // If requested, relayout with the given animation
-        if (animation != null) {
-            updateLayoutAlgorithm(true /* boundScroll */);
-            relayoutTaskViews(animation);
-        }
-
-        // Update the new front most task's action button
-        if (mScreenPinningEnabled && newFrontMostTask != null) {
-            TaskView frontTv = getChildViewForTask(newFrontMostTask);
-            if (frontTv != null) {
-                frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION);
-            }
-        }
-
-        // If there are no remaining tasks, then just close recents
-        if (mStack.getTaskCount() == 0) {
-            if (dismissRecentsIfAllRemoved) {
-                EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture
-                        ? R.string.recents_empty_message
-                        : R.string.recents_empty_message_dismissed_all));
-            } else {
-                EventBus.getDefault().send(new ShowEmptyViewEvent());
-            }
-        }
-    }
-
-    @Override
-    public void onStackTasksRemoved(TaskStack stack) {
-        // Reset the focused task
-        resetFocusedTask(getFocusedTask());
-
-        // Return all the views to the pool
-        List<TaskView> taskViews = new ArrayList<>();
-        taskViews.addAll(getTaskViews());
-        for (int i = taskViews.size() - 1; i >= 0; i--) {
-            mViewPool.returnViewToPool(taskViews.get(i));
-        }
-
-        // Remove all the ignore tasks
-        mIgnoreTasks.clear();
-
-        // If there are no remaining tasks, then just close recents
-        EventBus.getDefault().send(new AllTaskViewsDismissedEvent(
-                R.string.recents_empty_message_dismissed_all));
-    }
-
-    @Override
-    public void onStackTasksUpdated(TaskStack stack) {
-        if (!mFinishedLayoutAfterStackReload) {
-            return;
-        }
-
-        // Update the layout and immediately layout
-        updateLayoutAlgorithm(false /* boundScroll */);
-        relayoutTaskViews(AnimationProps.IMMEDIATE);
-
-        // Rebind all the task views.  This will not trigger new resources to be loaded
-        // unless they have actually changed
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            bindTaskView(tv, tv.getTask());
-        }
-    }
-
-    /**** ViewPoolConsumer Implementation ****/
-
-    @Override
-    public TaskView createView(Context context) {
-        if (LegacyRecentsImpl.getConfiguration().isGridEnabled) {
-            return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false);
-        } else {
-            return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
-        }
-    }
-
-    @Override
-    public void onReturnViewToPool(TaskView tv) {
-        final Task task = tv.getTask();
-
-        // Unbind the task from the task view
-        unbindTaskView(tv, task);
-
-        // Reset the view properties and view state
-        tv.clearAccessibilityFocus();
-        tv.resetViewProperties();
-        tv.setFocusedState(false, false /* requestViewFocus */);
-        tv.setClipViewInStack(false);
-        if (mScreenPinningEnabled) {
-            tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null);
-        }
-
-        // Detach the view from the hierarchy
-        detachViewFromParent(tv);
-        // Update the task views list after removing the task view
-        updateTaskViewsList();
-    }
-
-    @Override
-    public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
-        // Find the index where this task should be placed in the stack
-        int taskIndex = mStack.indexOfTask(task);
-        int insertIndex = findTaskViewInsertIndex(task, taskIndex);
-
-        // Add/attach the view to the hierarchy
-        if (isNewView) {
-            if (mInMeasureLayout) {
-                // If we are measuring the layout, then just add the view normally as it will be
-                // laid out during the layout pass
-                addView(tv, insertIndex);
-            } else {
-                // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout
-                // pass, and we should layout the new child ourselves
-                ViewGroup.LayoutParams params = tv.getLayoutParams();
-                if (params == null) {
-                    params = generateDefaultLayoutParams();
-                }
-                addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
-                measureTaskView(tv);
-                layoutTaskView(true /* changed */, tv);
-            }
-        } else {
-            attachViewToParent(tv, insertIndex, tv.getLayoutParams());
-        }
-        // Update the task views list after adding the new task view
-        updateTaskViewsList();
-
-        // Bind the task view to the new task
-        bindTaskView(tv, task);
-
-        // Set the new state for this view, including the callbacks and view clipping
-        tv.setCallbacks(this);
-        tv.setTouchEnabled(true);
-        tv.setClipViewInStack(true);
-        if (mFocusedTask == task) {
-            tv.setFocusedState(true, false /* requestViewFocus */);
-            if (mStartTimerIndicatorDuration > 0) {
-                // The timer indicator couldn't be started before, so start it now
-                tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration);
-                mStartTimerIndicatorDuration = 0;
-            }
-        }
-
-        // Restore the action button visibility if it is the front most task view
-        if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) {
-            tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
-        }
-    }
-
-    @Override
-    public boolean hasPreferredData(TaskView tv, Task preferredData) {
-        return (tv.getTask() == preferredData);
-    }
-
-    private void bindTaskView(TaskView tv, Task task) {
-        // Rebind the task and request that this task's data be filled into the TaskView
-        tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
-
-        // If the doze trigger has already fired, then update the state for this task view
-        if (mUIDozeTrigger.isAsleep() ||
-                useGridLayout() || LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            tv.setNoUserInteractionState();
-        }
-
-        if (task == mPrefetchingTask) {
-            task.notifyTaskDataLoaded(task.thumbnail, task.icon);
-        } else {
-            // Load the task data
-            LegacyRecentsImpl.getTaskLoader().loadTaskData(task);
-        }
-        LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
-    }
-
-    private void unbindTaskView(TaskView tv, Task task) {
-        if (task != mPrefetchingTask) {
-            // Report that this task's data is no longer being used
-            LegacyRecentsImpl.getTaskLoader().unloadTaskData(task);
-        }
-        LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
-    }
-
-    private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
-        Task t = null;
-        boolean somethingVisible = frontIndex != -1 && backIndex != -1;
-        if (somethingVisible && frontIndex < tasks.size() - 1) {
-            t = tasks.get(frontIndex + 1);
-        }
-        if (mPrefetchingTask != t) {
-            if (mPrefetchingTask != null) {
-                int index = tasks.indexOf(mPrefetchingTask);
-                if (index < backIndex || index > frontIndex) {
-                    LegacyRecentsImpl.getTaskLoader().unloadTaskData(mPrefetchingTask);
-                }
-            }
-            mPrefetchingTask = t;
-            if (t != null) {
-                LegacyRecentsImpl.getTaskLoader().loadTaskData(t);
-            }
-        }
-    }
-
-    private void clearPrefetchingTask() {
-        if (mPrefetchingTask != null) {
-            LegacyRecentsImpl.getTaskLoader().unloadTaskData(mPrefetchingTask);
-        }
-        mPrefetchingTask = null;
-    }
-
-    /**** TaskViewCallbacks Implementation ****/
-
-    @Override
-    public void onTaskViewClipStateChanged(TaskView tv) {
-        if (!mTaskViewsClipDirty) {
-            mTaskViewsClipDirty = true;
-            invalidate();
-        }
-    }
-
-    /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/
-
-    @Override
-    public void onFocusStateChanged(int prevFocusState, int curFocusState) {
-        if (mDeferredTaskViewLayoutAnimation == null) {
-            mUIDozeTrigger.poke();
-            relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE);
-        }
-    }
-
-    /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
-
-    @Override
-    public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
-        mUIDozeTrigger.poke();
-        if (animation != null) {
-            relayoutTaskViewsOnNextFrame(animation);
-        }
-
-        // In grid layout, the stack action button always remains visible.
-        if (mEnterAnimationComplete && !useGridLayout()) {
-            if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                // Show stack button when user drags down to show older tasks on low ram devices
-                if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible
-                        && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) {
-                    // Going up
-                    EventBus.getDefault().send(
-                            new ShowStackActionButtonEvent(true /* translate */));
-                }
-                return;
-            }
-            if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                    curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                    mStack.getTaskCount() > 0) {
-                EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
-            } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                    curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
-                EventBus.getDefault().send(new HideStackActionButtonEvent());
-            }
-        }
-    }
-
-    /**** EventBus Events ****/
-
-    public final void onBusEvent(PackagesChangedEvent event) {
-        // Compute which components need to be removed
-        ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
-                event.packageName, event.userId);
-
-        // For other tasks, just remove them directly if they no longer exist
-        ArrayList<Task> tasks = mStack.getTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            final Task t = tasks.get(i);
-            if (removedComponents.contains(t.key.getComponent())) {
-                final TaskView tv = getChildViewForTask(t);
-                if (tv != null) {
-                    // For visible children, defer removing the task until after the animation
-                    tv.dismissTask();
-                } else {
-                    // Otherwise, remove the task from the stack immediately
-                    mStack.removeTask(t, AnimationProps.IMMEDIATE, false /* fromDockGesture */);
-                }
-            }
-        }
-    }
-
-    public final void onBusEvent(LaunchTaskEvent event) {
-        // Cancel any doze triggers once a task is launched
-        mUIDozeTrigger.stopDozing();
-    }
-
-    public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
-        if (mStack.getTaskCount() > 0) {
-            Task mostRecentTask = mStack.getFrontMostTask();
-            launchTask(mostRecentTask);
-        }
-    }
-
-    public final void onBusEvent(ShowStackActionButtonEvent event) {
-        mStackActionButtonVisible = true;
-    }
-
-    public final void onBusEvent(HideStackActionButtonEvent event) {
-        mStackActionButtonVisible = false;
-    }
-
-    public final void onBusEvent(LaunchNextTaskRequestEvent event) {
-        if (!mFinishedLayoutAfterStackReload) {
-            mLaunchNextAfterFirstMeasure = true;
-            return;
-        }
-
-        if (mStack.getTaskCount() == 0) {
-            if (RecentsImpl.getLastPipTime() != -1) {
-                EventBus.getDefault().send(new ExpandPipEvent());
-                MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
-                        "pip");
-            } else {
-                // If there are no tasks, then just hide recents back to home.
-                EventBus.getDefault().send(new HideRecentsEvent(false, true));
-            }
-            return;
-        }
-
-        if (!LegacyRecentsImpl.getConfiguration().getLaunchState().launchedFromPipApp
-                && mStack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime())) {
-            // If the launch task is in the pinned stack, then expand the PiP now
-            EventBus.getDefault().send(new ExpandPipEvent());
-            MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, "pip");
-        } else {
-            final Task launchTask = mStack.getNextLaunchTarget();
-            if (launchTask != null) {
-                // Defer launching the task until the PiP menu has been dismissed (if it is
-                // showing at all)
-                HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
-                hideMenuEvent.addPostAnimationCallback(() -> {
-                    launchTask(launchTask);
-                });
-                EventBus.getDefault().send(hideMenuEvent);
-                MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
-                        launchTask.key.getComponent().toString());
-            }
-        }
-    }
-
-    public final void onBusEvent(LaunchTaskStartedEvent event) {
-        mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested,
-                event.getAnimationTrigger());
-    }
-
-    public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
-        // Stop any scrolling
-        mTouchHandler.cancelNonDismissTaskAnimations();
-        mStackScroller.stopScroller();
-        mStackScroller.stopBoundScrollAnimation();
-        cancelDeferredTaskViewLayoutAnimation();
-
-        // Start the task animations
-        mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
-
-        // Dismiss the grid task view focus frame
-        if (mTaskViewFocusFrame != null) {
-            mTaskViewFocusFrame.moveGridTaskViewFocus(null);
-        }
-    }
-
-    public final void onBusEvent(DismissFocusedTaskViewEvent event) {
-        if (mFocusedTask != null) {
-            if (mTaskViewFocusFrame != null) {
-                mTaskViewFocusFrame.moveGridTaskViewFocus(null);
-            }
-            TaskView tv = getChildViewForTask(mFocusedTask);
-            if (tv != null) {
-                tv.dismissTask();
-            }
-            resetFocusedTask(mFocusedTask);
-        }
-    }
-
-    public final void onBusEvent(DismissTaskViewEvent event) {
-        // For visible children, defer removing the task until after the animation
-        mAnimationHelper.startDeleteTaskAnimation(
-                event.taskView, useGridLayout(), event.getAnimationTrigger());
-    }
-
-    public final void onBusEvent(final DismissAllTaskViewsEvent event) {
-        // Keep track of the tasks which will have their data removed
-        ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks());
-        mAnimationHelper.startDeleteAllTasksAnimation(
-                getTaskViews(), useGridLayout(), event.getAnimationTrigger());
-        event.addPostAnimationCallback(new Runnable() {
-            @Override
-            public void run() {
-                // Announce for accessibility
-                announceForAccessibility(getContext().getString(
-                        R.string.accessibility_recents_all_items_dismissed));
-
-                // Remove all tasks and delete the task data for all tasks
-                mStack.removeAllTasks(true /* notifyStackChanges */);
-                for (int i = tasks.size() - 1; i >= 0; i--) {
-                    EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
-                }
-
-                MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL);
-            }
-        });
-
-    }
-
-    public final void onBusEvent(TaskViewDismissedEvent event) {
-        // Announce for accessibility
-        announceForAccessibility(getContext().getString(
-                R.string.accessibility_recents_item_dismissed, event.task.title));
-
-        if (useGridLayout() && event.animation != null) {
-            event.animation.setListener(new AnimatorListenerAdapter() {
-                public void onAnimationEnd(Animator animator) {
-                    if (mTaskViewFocusFrame != null) {
-                        // Resize the grid layout task view focus frame
-                        mTaskViewFocusFrame.resize();
-                    }
-                }
-            });
-        }
-
-        // Remove the task from the stack
-        mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
-        EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
-        if (mStack.getTaskCount() > 0 && LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
-        }
-
-        MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
-                event.task.key.getComponent().toString());
-    }
-
-    public final void onBusEvent(FocusNextTaskViewEvent event) {
-        // Stop any scrolling
-        mStackScroller.stopScroller();
-        mStackScroller.stopBoundScrollAnimation();
-
-        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0);
-    }
-
-    public final void onBusEvent(FocusPreviousTaskViewEvent event) {
-        // Stop any scrolling
-        mStackScroller.stopScroller();
-        mStackScroller.stopBoundScrollAnimation();
-
-        setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */);
-    }
-
-    public final void onBusEvent(NavigateTaskViewEvent event) {
-        if (useGridLayout()) {
-            final int taskCount = mStack.getTaskCount();
-            final int currentIndex = mStack.indexOfTask(getFocusedTask());
-            final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount,
-                    currentIndex, event.direction);
-            setFocusedTask(nextIndex, false, true);
-        } else {
-            switch (event.direction) {
-                case UP:
-                    EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
-                    break;
-                case DOWN:
-                    EventBus.getDefault().send(new FocusNextTaskViewEvent());
-                    break;
-            }
-        }
-    }
-
-    public final void onBusEvent(UserInteractionEvent event) {
-        // Poke the doze trigger on user interaction
-        mUIDozeTrigger.poke();
-
-        RecentsDebugFlags debugFlags = LegacyRecentsImpl.getDebugFlags();
-        if (mFocusedTask != null) {
-            TaskView tv = getChildViewForTask(mFocusedTask);
-            if (tv != null) {
-                tv.getHeaderView().cancelFocusTimerIndicator();
-            }
-        }
-    }
-
-    public final void onBusEvent(DragStartEvent event) {
-        // Ensure that the drag task is not animated
-        addIgnoreTask(event.task);
-
-        // Enlarge the dragged view slightly
-        float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
-        mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
-                mTmpTransform, null);
-        mTmpTransform.scale = finalScale;
-        mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
-        mTmpTransform.dimAlpha = 0f;
-        updateTaskViewToTransform(event.taskView, mTmpTransform,
-                new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
-    }
-
-    public final void onBusEvent(DragDropTargetChangedEvent event) {
-        AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN);
-        boolean ignoreTaskOverrides = false;
-        if (event.dropTarget instanceof DockState) {
-            // Calculate the new task stack bounds that matches the window size that Recents will
-            // have after the drop
-            final DockState dockState = (DockState) event.dropTarget;
-            Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
-            // When docked, the nav bar insets are consumed and the activity is measured without
-            // insets.  However, the window bounds include the insets, so we need to subtract them
-            // here to make them identical.
-            int height = getMeasuredHeight();
-            height -= systemInsets.bottom;
-            systemInsets.bottom = 0;
-            mStackBounds.set(dockState.getDockedTaskStackBounds(mDisplayRect, getMeasuredWidth(),
-                    height, mDividerSize, systemInsets,
-                    mLayoutAlgorithm, getResources(), mWindowRect));
-            mLayoutAlgorithm.setSystemInsets(systemInsets);
-            mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
-            updateLayoutAlgorithm(true /* boundScroll */);
-            ignoreTaskOverrides = true;
-        } else {
-            // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
-            // task view, so add it back to the ignore set after updating the layout
-            removeIgnoreTask(event.task);
-            updateLayoutToStableBounds();
-            addIgnoreTask(event.task);
-        }
-        relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides);
-    }
-
-    public final void onBusEvent(final DragEndEvent event) {
-        // We don't handle drops on the dock regions
-        if (event.dropTarget instanceof DockState) {
-            // However, we do need to reset the overrides, since the last state of this task stack
-            // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
-            mLayoutAlgorithm.clearUnfocusedTaskOverrides();
-            return;
-        }
-
-        // Restore the task, so that relayout will apply to it below
-        removeIgnoreTask(event.task);
-
-        // Convert the dragging task view back to its final layout-space rect
-        Utilities.setViewFrameFromTranslation(event.taskView);
-
-        // Animate all the tasks into place
-        ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
-        animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN,
-                event.getAnimationTrigger().decrementOnAnimationEnd()));
-        relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN));
-        event.getAnimationTrigger().increment();
-    }
-
-    public final void onBusEvent(final DragEndCancelledEvent event) {
-        // Restore the pre-drag task stack bounds, including the dragging task view
-        removeIgnoreTask(event.task);
-        updateLayoutToStableBounds();
-
-        // Convert the dragging task view back to its final layout-space rect
-        Utilities.setViewFrameFromTranslation(event.taskView);
-
-        // Animate all the tasks into place
-        ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
-        animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN,
-                event.getAnimationTrigger().decrementOnAnimationEnd()));
-        relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN));
-        event.getAnimationTrigger().increment();
-    }
-
-    public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
-        mEnterAnimationComplete = true;
-        tryStartEnterAnimation();
-    }
-
-    private void tryStartEnterAnimation() {
-        if (!mStackReloaded || !mFinishedLayoutAfterStackReload || !mEnterAnimationComplete) {
-            return;
-        }
-
-        if (mStack.getTaskCount() > 0) {
-            // Start the task enter animations
-            ReferenceCountedTrigger trigger = new ReferenceCountedTrigger();
-            mAnimationHelper.startEnterAnimation(trigger);
-
-            // Add a runnable to the post animation ref counter to clear all the views
-            trigger.addLastDecrementRunnable(() -> {
-                // Start the dozer to trigger to trigger any UI that shows after a timeout
-                mUIDozeTrigger.startDozing();
-
-                // Update the focused state here -- since we only set the focused task without
-                // requesting view focus in onFirstLayout(), actually request view focus and
-                // animate the focused state if we are alt-tabbing now, after the window enter
-                // animation is completed
-                if (mFocusedTask != null) {
-                    RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-                    RecentsActivityLaunchState launchState = config.getLaunchState();
-                    setFocusedTask(mStack.indexOfTask(mFocusedTask),
-                            false /* scrollToTask */, launchState.launchedWithAltTab);
-                    TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
-                    if (mTouchExplorationEnabled && focusedTaskView != null) {
-                        focusedTaskView.requestAccessibilityFocus();
-                    }
-                }
-            });
-        }
-
-        // This flag is only used to choreograph the enter animation, so we can reset it here
-        mStackReloaded = false;
-    }
-
-    public final void onBusEvent(final MultiWindowStateChangedEvent event) {
-        if (event.inMultiWindow || !event.showDeferredAnimation) {
-            setTasks(event.stack, true /* allowNotifyStackChanges */);
-        } else {
-            // Reset the launch state before handling the multiwindow change
-            RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-            launchState.reset();
-
-            // Defer until the next frame to ensure that we have received all the system insets, and
-            // initial layout updates
-            event.getAnimationTrigger().increment();
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    // Scroll the stack to the front to see the undocked task
-                    mAnimationHelper.startNewStackScrollAnimation(event.stack,
-                            event.getAnimationTrigger());
-                    event.getAnimationTrigger().decrement();
-                }
-            });
-        }
-    }
-
-    public final void onBusEvent(ConfigurationChangedEvent event) {
-        if (event.fromDeviceOrientationChange) {
-            mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
-            mDisplayRect = LegacyRecentsImpl.getSystemServices().getDisplayRect();
-
-            // Always stop the scroller, otherwise, we may continue setting the stack scroll to the
-            // wrong bounds in the new layout
-            mStackScroller.stopScroller();
-        }
-        reloadOnConfigurationChange();
-
-        // Notify the task views of the configuration change so they can reload their resources
-        if (!event.fromMultiWindow) {
-            mTmpTaskViews.clear();
-            mTmpTaskViews.addAll(getTaskViews());
-            mTmpTaskViews.addAll(mViewPool.getViews());
-            int taskViewCount = mTmpTaskViews.size();
-            for (int i = 0; i < taskViewCount; i++) {
-                mTmpTaskViews.get(i).onConfigurationChanged();
-            }
-        }
-
-        // Update the Clear All button in case we're switching in or out of grid layout.
-        updateStackActionButtonVisibility();
-
-        // Trigger a new layout and update to the initial state if necessary. When entering split
-        // screen, the multi-window configuration change event can happen after the stack is already
-        // reloaded (but pending measure/layout), in this case, do not override the intiial state
-        // and just wait for the upcoming measure/layout pass.
-        if (event.fromMultiWindow && mInitialState == INITIAL_STATE_UPDATE_NONE) {
-            mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
-            requestLayout();
-        } else if (event.fromDeviceOrientationChange) {
-            mInitialState = INITIAL_STATE_UPDATE_ALL;
-            requestLayout();
-        }
-    }
-
-    public final void onBusEvent(RecentsGrowingEvent event) {
-        mResetToInitialStateWhenResized = true;
-    }
-
-    public final void onBusEvent(RecentsVisibilityChangedEvent event) {
-        if (!event.visible) {
-            if (mTaskViewFocusFrame != null) {
-                mTaskViewFocusFrame.moveGridTaskViewFocus(null);
-            }
-
-            List<TaskView> taskViews = new ArrayList<>(getTaskViews());
-            for (int i = 0; i < taskViews.size(); i++) {
-                mViewPool.returnViewToPool(taskViews.get(i));
-            }
-            clearPrefetchingTask();
-
-            // We can not reset mEnterAnimationComplete in onReload() because when docking the top
-            // task, we can receive the enter animation callback before onReload(), so reset it
-            // here onces Recents is not visible
-            mEnterAnimationComplete = false;
-        }
-    }
-
-    public final void onBusEvent(ActivityPinnedEvent event) {
-        // If an activity enters PiP while Recents is open, remove the stack task associated with
-        // the new PiP task
-        Task removeTask = mStack.findTaskWithId(event.taskId);
-        if (removeTask != null) {
-            // In this case, we remove the task, but if the last task is removed, don't dismiss
-            // Recents to home
-            mStack.removeTask(removeTask, AnimationProps.IMMEDIATE, false /* fromDockGesture */,
-                    false /* dismissRecentsIfAllRemoved */);
-        }
-        updateLayoutAlgorithm(false /* boundScroll */);
-        updateToInitialState();
-    }
-
-    public void reloadOnConfigurationChange() {
-        mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
-        mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
-    }
-
-    /**
-     * Returns the insert index for the task in the current set of task views. If the given task
-     * is already in the task view list, then this method returns the insert index assuming it
-     * is first removed at the previous index.
-     *
-     * @param task the task we are finding the index for
-     * @param taskIndex the index of the task in the stack
-     */
-    private int findTaskViewInsertIndex(Task task, int taskIndex) {
-        if (taskIndex != -1) {
-            List<TaskView> taskViews = getTaskViews();
-            boolean foundTaskView = false;
-            int taskViewCount = taskViews.size();
-            for (int i = 0; i < taskViewCount; i++) {
-                Task tvTask = taskViews.get(i).getTask();
-                if (tvTask == task) {
-                    foundTaskView = true;
-                } else if (taskIndex < mStack.indexOfTask(tvTask)) {
-                    if (foundTaskView) {
-                        return i - 1;
-                    } else {
-                        return i;
-                    }
-                }
-            }
-        }
-        return -1;
-    }
-
-    private void launchTask(Task task) {
-        // Stop all animations
-        cancelAllTaskViewAnimations();
-
-        float curScroll = mStackScroller.getStackScroll();
-        float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(task);
-        float absScrollDiff = Math.abs(targetScroll - curScroll);
-        if (getChildViewForTask(task) == null || absScrollDiff > 0.35f) {
-            int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION +
-                    absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION);
-            mStackScroller.animateScroll(targetScroll,
-                    duration, new Runnable() {
-                        @Override
-                        public void run() {
-                            EventBus.getDefault().send(new LaunchTaskEvent(
-                                    getChildViewForTask(task), task, null,
-                                    false /* screenPinningRequested */));
-                        }
-                    });
-        } else {
-            EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null,
-                    false /* screenPinningRequested */));
-        }
-    }
-
-    /**
-     * Check whether we should use the grid layout.
-     */
-    public boolean useGridLayout() {
-        return mLayoutAlgorithm.useGridLayout();
-    }
-
-    /**
-     * Reads current system flags related to accessibility and screen pinning.
-     */
-    private void readSystemFlags() {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
-        mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isScreenPinningEnabled()
-                && !ActivityManagerWrapper.getInstance().isLockToAppActive();
-    }
-
-    private void updateStackActionButtonVisibility() {
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return;
-        }
-
-        // Always show the button in grid layout.
-        if (useGridLayout() ||
-                (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                        mStack.getTaskCount() > 0)) {
-            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
-        } else {
-            EventBus.getDefault().send(new HideStackActionButtonEvent());
-        }
-    }
-
-    /**
-     * Returns the task to focus given the current launch state.
-     */
-    private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks,
-            boolean useGridLayout) {
-        if (launchState.launchedFromApp) {
-            if (useGridLayout) {
-                // If coming from another app to the grid layout, focus the front most task
-                return numTasks - 1;
-            }
-
-            // If coming from another app, focus the next task
-            return Math.max(0, numTasks - 2);
-        } else {
-            // If coming from home, focus the front most task
-            return numTasks - 1;
-        }
-    }
-
-    /**
-     * Updates {@param transforms} to be the same size as {@param tasks}.
-     */
-    private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
-        // We can reuse the task transforms where possible to reduce object allocation
-        int taskTransformCount = transforms.size();
-        int taskCount = tasks.size();
-        if (taskTransformCount < taskCount) {
-            // If there are less transforms than tasks, then add as many transforms as necessary
-            for (int i = taskTransformCount; i < taskCount; i++) {
-                transforms.add(new TaskViewTransform());
-            }
-        } else if (taskTransformCount > taskCount) {
-            // If there are more transforms than tasks, then just subset the transform list
-            transforms.subList(taskCount, taskTransformCount).clear();
-        }
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-        String id = Integer.toHexString(System.identityHashCode(this));
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" hasDefRelayout=");
-        writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N");
-        writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N");
-        writer.print(" awaitingStackReload="); writer.print(mFinishedLayoutAfterStackReload ? "Y" : "N");
-        writer.print(" initialState="); writer.print(mInitialState);
-        writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N");
-        writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N");
-        writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N");
-        writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N");
-        writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size());
-        writer.print(" numViewPool="); writer.print(mViewPool.getViews().size());
-        writer.print(" stableStackBounds="); writer.print(
-                Utilities.dumpRect(mStableStackBounds));
-        writer.print(" stackBounds="); writer.print(
-                Utilities.dumpRect(mStackBounds));
-        writer.print(" stableWindow="); writer.print(
-                Utilities.dumpRect(mStableWindowRect));
-        writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect));
-        writer.print(" display="); writer.print(Utilities.dumpRect(mDisplayRect));
-        writer.print(" orientation="); writer.print(mDisplayOrientation);
-        writer.print(" [0x"); writer.print(id); writer.print("]");
-        writer.println();
-
-        if (mFocusedTask != null) {
-            writer.print(innerPrefix);
-            writer.print("Focused task: ");
-            mFocusedTask.dump("", writer);
-        }
-
-        int numTaskViews = mTaskViews.size();
-        for (int i = 0; i < numTaskViews; i++) {
-            mTaskViews.get(i).dump(innerPrefix, writer);
-        }
-
-        mLayoutAlgorithm.dump(innerPrefix, writer);
-        mStackScroller.dump(innerPrefix, writer);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewScroller.java
deleted file mode 100644
index 42efe59..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.util.Property;
-import android.view.ViewConfiguration;
-import android.view.ViewDebug;
-import android.widget.OverScroller;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-
-import java.io.PrintWriter;
-
-/* The scrolling logic for a TaskStackView */
-public class TaskStackViewScroller {
-
-    private static final String TAG = "TaskStackViewScroller";
-    private static final boolean DEBUG = false;
-
-    public interface TaskStackViewScrollerCallbacks {
-        void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
-    }
-
-    /**
-     * A Property wrapper around the <code>stackScroll</code> functionality handled by the
-     * {@link #setStackScroll(float)} and
-     * {@link #getStackScroll()} methods.
-     */
-    private static final Property<TaskStackViewScroller, Float> STACK_SCROLL =
-            new FloatProperty<TaskStackViewScroller>("stackScroll") {
-                @Override
-                public void setValue(TaskStackViewScroller object, float value) {
-                    object.setStackScroll(value);
-                }
-
-                @Override
-                public Float get(TaskStackViewScroller object) {
-                    return object.getStackScroll();
-                }
-            };
-
-    Context mContext;
-    TaskStackLayoutAlgorithm mLayoutAlgorithm;
-    TaskStackViewScrollerCallbacks mCb;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    float mStackScrollP;
-    @ViewDebug.ExportedProperty(category="recents")
-    float mLastDeltaP = 0f;
-    float mFlingDownScrollP;
-    int mFlingDownY;
-
-    OverScroller mScroller;
-    ObjectAnimator mScrollAnimator;
-    float mFinalAnimatedScroll;
-
-    final FlingAnimationUtils mFlingAnimationUtils;
-
-    public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb,
-            TaskStackLayoutAlgorithm layoutAlgorithm) {
-        mContext = context;
-        mCb = cb;
-        mScroller = new OverScroller(context);
-        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            mScroller.setFriction(0.06f);
-        }
-        mLayoutAlgorithm = layoutAlgorithm;
-        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
-    }
-
-    /** Resets the task scroller. */
-    void reset() {
-        mStackScrollP = 0f;
-        mLastDeltaP = 0f;
-    }
-
-    void resetDeltaScroll() {
-        mLastDeltaP = 0f;
-    }
-
-    /** Gets the current stack scroll */
-    public float getStackScroll() {
-        return mStackScrollP;
-    }
-
-    /**
-     * Sets the current stack scroll immediately.
-     */
-    public void setStackScroll(float s) {
-        setStackScroll(s, AnimationProps.IMMEDIATE);
-    }
-
-    /**
-     * Sets the current stack scroll immediately, and returns the difference between the target
-     * scroll and the actual scroll after accounting for the effect on the focus state.
-     */
-    public float setDeltaStackScroll(float downP, float deltaP) {
-        float targetScroll = downP + deltaP;
-        float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll,
-                mStackScrollP);
-        setStackScroll(newScroll, AnimationProps.IMMEDIATE);
-        mLastDeltaP = deltaP;
-        return newScroll - targetScroll;
-    }
-
-    /**
-     * Sets the current stack scroll, but indicates to the callback the preferred animation to
-     * update to this new scroll.
-     */
-    public void setStackScroll(float newScroll, AnimationProps animation) {
-        float prevScroll = mStackScrollP;
-        mStackScrollP = newScroll;
-        if (mCb != null) {
-            mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation);
-        }
-    }
-
-    /**
-     * Sets the current stack scroll to the initial state when you first enter recents.
-     * @return whether the stack progress changed.
-     */
-    public boolean setStackScrollToInitialState() {
-        float prevScroll = mStackScrollP;
-        setStackScroll(mLayoutAlgorithm.mInitialScrollP);
-        return Float.compare(prevScroll, mStackScrollP) != 0;
-    }
-
-    /**
-     * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
-     */
-    public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
-            int overscroll) {
-        if (DEBUG) {
-            Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
-                    ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
-        }
-        mFlingDownScrollP = downScrollP;
-        mFlingDownY = downY;
-        mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
-    }
-
-    /** Bounds the current scroll if necessary */
-    public boolean boundScroll() {
-        float curScroll = getStackScroll();
-        float newScroll = getBoundedStackScroll(curScroll);
-        if (Float.compare(newScroll, curScroll) != 0) {
-            setStackScroll(newScroll);
-            return true;
-        }
-        return false;
-    }
-
-    /** Returns the bounded stack scroll */
-    float getBoundedStackScroll(float scroll) {
-        return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP);
-    }
-
-    /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
-    float getScrollAmountOutOfBounds(float scroll) {
-        if (scroll < mLayoutAlgorithm.mMinScrollP) {
-            return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
-        } else if (scroll > mLayoutAlgorithm.mMaxScrollP) {
-            return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);
-        }
-        return 0f;
-    }
-
-    /** Returns whether the specified scroll is out of bounds */
-    boolean isScrollOutOfBounds() {
-        return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
-    }
-
-    /**
-     * Scrolls the closest task and snaps into place. Only used in recents for low ram devices.
-     * @param velocity of scroll
-     */
-    void scrollToClosestTask(int velocity) {
-        float stackScroll = getStackScroll();
-
-        // Skip if not in low ram layout and if the scroll is out of min and max bounds
-        if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice || stackScroll < mLayoutAlgorithm.mMinScrollP
-                || stackScroll > mLayoutAlgorithm.mMaxScrollP) {
-            return;
-        }
-        TaskStackLowRamLayoutAlgorithm algorithm = mLayoutAlgorithm.mTaskStackLowRamLayoutAlgorithm;
-
-        float flingThreshold = ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity();
-        if (Math.abs(velocity) > flingThreshold) {
-            int minY = algorithm.percentageToScroll(mLayoutAlgorithm.mMinScrollP);
-            int maxY = algorithm.percentageToScroll(mLayoutAlgorithm.mMaxScrollP);
-
-            // Calculate the fling and snap to closest task from final y position, computeScroll()
-            // never runs when cancelled with animateScroll() and the overscroll is not calculated
-            // here
-            fling(0 /* downScrollP */, 0 /* downY */, algorithm.percentageToScroll(stackScroll),
-                    -velocity, minY, maxY, 0 /* overscroll */);
-            float pos = algorithm.scrollToPercentage(mScroller.getFinalY());
-
-            float newScrollP = algorithm.getClosestTaskP(pos, mLayoutAlgorithm.mNumStackTasks,
-                    velocity);
-            ValueAnimator animator = ObjectAnimator.ofFloat(stackScroll, newScrollP);
-            mFlingAnimationUtils.apply(animator, algorithm.percentageToScroll(stackScroll),
-                    algorithm.percentageToScroll(newScrollP), velocity);
-            animateScroll(newScrollP, (int) animator.getDuration(), animator.getInterpolator(),
-                    null /* postRunnable */);
-        } else {
-            float newScrollP = algorithm.getClosestTaskP(stackScroll,
-                    mLayoutAlgorithm.mNumStackTasks, velocity);
-            animateScroll(newScrollP, 300, Interpolators.ACCELERATE_DECELERATE,
-                    null /* postRunnable */);
-        }
-    }
-
-    /** Animates the stack scroll into bounds */
-    ObjectAnimator animateBoundScroll() {
-        // TODO: Take duration for snap back
-        float curScroll = getStackScroll();
-        float newScroll = getBoundedStackScroll(curScroll);
-        if (Float.compare(newScroll, curScroll) != 0) {
-            // Start a new scroll animation
-            animateScroll(newScroll, null /* postScrollRunnable */);
-        }
-        return mScrollAnimator;
-    }
-
-    /** Animates the stack scroll */
-    void animateScroll(float newScroll, final Runnable postRunnable) {
-        int duration = mContext.getResources().getInteger(
-                R.integer.recents_animate_task_stack_scroll_duration);
-        animateScroll(newScroll, duration, postRunnable);
-    }
-
-    /** Animates the stack scroll */
-    void animateScroll(float newScroll, int duration, final Runnable postRunnable) {
-        animateScroll(newScroll, duration, Interpolators.LINEAR_OUT_SLOW_IN, postRunnable);
-    }
-
-    /** Animates the stack scroll with time interpolator */
-    void animateScroll(float newScroll, int duration, TimeInterpolator interpolator,
-            final Runnable postRunnable) {
-        ObjectAnimator an = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
-        an.setDuration(duration);
-        an.setInterpolator(interpolator);
-        animateScroll(newScroll, an, postRunnable);
-    }
-
-    /** Animates the stack scroll with animator */
-    private void animateScroll(float newScroll, ObjectAnimator animator,
-            final Runnable postRunnable) {
-        // Finish any current scrolling animations
-        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
-            setStackScroll(mFinalAnimatedScroll);
-            mScroller.forceFinished(true);
-        }
-        stopScroller();
-        stopBoundScrollAnimation();
-
-        if (Float.compare(mStackScrollP, newScroll) != 0) {
-            mFinalAnimatedScroll = newScroll;
-            mScrollAnimator = animator;
-            mScrollAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (postRunnable != null) {
-                        postRunnable.run();
-                    }
-                    mScrollAnimator.removeAllListeners();
-                }
-            });
-            mScrollAnimator.start();
-        } else {
-            if (postRunnable != null) {
-                postRunnable.run();
-            }
-        }
-    }
-
-    /** Aborts any current stack scrolls */
-    void stopBoundScrollAnimation() {
-        Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator);
-    }
-
-    /**** OverScroller ****/
-
-    /** Called from the view draw, computes the next scroll. */
-    boolean computeScroll() {
-        if (mScroller.computeScrollOffset()) {
-            float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
-            mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP);
-            if (DEBUG) {
-                Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP));
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /** Returns whether the overscroller is scrolling. */
-    boolean isScrolling() {
-        return !mScroller.isFinished();
-    }
-
-    float getScrollVelocity() {
-        return mScroller.getCurrVelocity();
-    }
-
-    /** Stops the scroller and any current fling. */
-    void stopScroller() {
-        if (!mScroller.isFinished()) {
-            mScroller.abortAnimation();
-        }
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" stackScroll:"); writer.print(mStackScrollP);
-        writer.println();
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
deleted file mode 100644
index a7fb4fa..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ /dev/null
@@ -1,706 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Path;
-import android.util.ArrayMap;
-import android.util.MutableBoolean;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewDebug;
-import android.view.ViewParent;
-import android.view.animation.Interpolator;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
-import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.misc.FreePathInterpolator;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Handles touch events for a TaskStackView.
- */
-class TaskStackViewTouchHandler implements SwipeHelper.Callback {
-
-    private static final int INACTIVE_POINTER_ID = -1;
-    private static final float CHALLENGING_SWIPE_ESCAPE_VELOCITY = 800f; // dp/sec
-    // The min overscroll is the amount of task progress overscroll we want / the max overscroll
-    // curve value below
-    private static final float MAX_OVERSCROLL = 0.7f / 0.3f;
-    private static final Interpolator OVERSCROLL_INTERP;
-    static {
-        Path OVERSCROLL_PATH = new Path();
-        OVERSCROLL_PATH.moveTo(0, 0);
-        OVERSCROLL_PATH.cubicTo(0.2f, 0.175f, 0.25f, 0.3f, 1f, 0.3f);
-        OVERSCROLL_INTERP = new FreePathInterpolator(OVERSCROLL_PATH);
-    }
-
-    Context mContext;
-    TaskStackView mSv;
-    TaskStackViewScroller mScroller;
-    VelocityTracker mVelocityTracker;
-    FlingAnimationUtils mFlingAnimUtils;
-    ValueAnimator mScrollFlingAnimator;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    boolean mIsScrolling;
-    float mDownScrollP;
-    int mDownX, mDownY;
-    int mLastY;
-    int mActivePointerId = INACTIVE_POINTER_ID;
-    int mOverscrollSize;
-    TaskView mActiveTaskView = null;
-
-    int mMinimumVelocity;
-    int mMaximumVelocity;
-    // The scroll touch slop is used to calculate when we start scrolling
-    int mScrollTouchSlop;
-    // Used to calculate when a tap is outside a task view rectangle.
-    final int mWindowTouchSlop;
-
-    private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent();
-
-    // The current and final set of task transforms, sized to match the list of tasks in the stack
-    private ArrayList<Task> mCurrentTasks = new ArrayList<>();
-    private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
-    private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>();
-    private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>();
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-    private float mTargetStackScroll;
-
-    SwipeHelper mSwipeHelper;
-    boolean mInterceptedBySwipeHelper;
-
-    public TaskStackViewTouchHandler(Context context, TaskStackView sv,
-            TaskStackViewScroller scroller, FalsingManager falsingManager) {
-        Resources res = context.getResources();
-        ViewConfiguration configuration = ViewConfiguration.get(context);
-        mContext = context;
-        mSv = sv;
-        mScroller = scroller;
-        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
-        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
-        mScrollTouchSlop = configuration.getScaledTouchSlop();
-        mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
-        mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
-        mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_fling_overscroll_distance);
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context, falsingManager) {
-            @Override
-            protected float getSize(View v) {
-                return getScaledDismissSize();
-            }
-
-            @Override
-            protected void prepareDismissAnimation(View v, Animator anim) {
-                mSwipeHelperAnimations.put(v, anim);
-            }
-
-            @Override
-            protected void prepareSnapBackAnimation(View v, Animator anim) {
-                anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-                mSwipeHelperAnimations.put(v, anim);
-            }
-
-            @Override
-            protected float getUnscaledEscapeVelocity() {
-                return CHALLENGING_SWIPE_ESCAPE_VELOCITY;
-            }
-
-            @Override
-            protected long getMaxEscapeAnimDuration() {
-                return 700;
-            }
-        };
-        mSwipeHelper.setDisableHardwareLayers(true);
-    }
-
-    /** Velocity tracker helpers */
-    void initOrResetVelocityTracker() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        } else {
-            mVelocityTracker.clear();
-        }
-    }
-    void recycleVelocityTracker() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-    }
-
-    /** Touch preprocessing for handling below */
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Pass through to swipe helper if we are swiping
-        mInterceptedBySwipeHelper = isSwipingEnabled() && mSwipeHelper.onInterceptTouchEvent(ev);
-        if (mInterceptedBySwipeHelper) {
-            return true;
-        }
-
-        return handleTouchEvent(ev);
-    }
-
-    /** Handles touch events once we have intercepted them */
-    public boolean onTouchEvent(MotionEvent ev) {
-        // Pass through to swipe helper if we are swiping
-        if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
-            return true;
-        }
-
-        handleTouchEvent(ev);
-        return true;
-    }
-
-    /**
-     * Finishes all scroll-fling and non-dismissing animations currently running.
-     */
-    public void cancelNonDismissTaskAnimations() {
-        Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
-        if (!mSwipeHelperAnimations.isEmpty()) {
-            // For the non-dismissing tasks, freeze the position into the task overrides
-            List<TaskView> taskViews = mSv.getTaskViews();
-            for (int i = taskViews.size() - 1; i >= 0; i--) {
-                TaskView tv = taskViews.get(i);
-
-                if (mSv.isIgnoredTask(tv.getTask())) {
-                    continue;
-                }
-
-                tv.cancelTransformAnimation();
-                mSv.getStackAlgorithm().addUnfocusedTaskOverride(tv, mTargetStackScroll);
-            }
-            mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
-            // Update the scroll to the final scroll position from onBeginDrag()
-            mSv.getScroller().setStackScroll(mTargetStackScroll, null);
-
-            mSwipeHelperAnimations.clear();
-        }
-        mActiveTaskView = null;
-    }
-
-    private boolean handleTouchEvent(MotionEvent ev) {
-        // Short circuit if we have no children
-        if (mSv.getTaskViews().size() == 0) {
-            return false;
-        }
-
-        final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
-        int action = ev.getAction();
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                // Stop the current scroll if it is still flinging
-                mScroller.stopScroller();
-                mScroller.stopBoundScrollAnimation();
-                mScroller.resetDeltaScroll();
-                cancelNonDismissTaskAnimations();
-                mSv.cancelDeferredTaskViewLayoutAnimation();
-
-                // Save the touch down info
-                mDownX = (int) ev.getX();
-                mDownY = (int) ev.getY();
-                mLastY = mDownY;
-                mDownScrollP = mScroller.getStackScroll();
-                mActivePointerId = ev.getPointerId(0);
-                mActiveTaskView = findViewAtPoint(mDownX, mDownY);
-
-                // Initialize the velocity tracker
-                initOrResetVelocityTracker();
-                mVelocityTracker.addMovement(ev);
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                final int index = ev.getActionIndex();
-                mActivePointerId = ev.getPointerId(index);
-                mDownX = (int) ev.getX(index);
-                mDownY = (int) ev.getY(index);
-                mLastY = mDownY;
-                mDownScrollP = mScroller.getStackScroll();
-                mScroller.resetDeltaScroll();
-                mVelocityTracker.addMovement(ev);
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                if (activePointerIndex == -1) {
-                    break;
-                }
-                int y = (int) ev.getY(activePointerIndex);
-                int x = (int) ev.getX(activePointerIndex);
-                if (!mIsScrolling) {
-                    int yDiff = Math.abs(y - mDownY);
-                    int xDiff = Math.abs(x - mDownX);
-                    if (Math.abs(y - mDownY) > mScrollTouchSlop && yDiff > xDiff) {
-                        mIsScrolling = true;
-                        float stackScroll = mScroller.getStackScroll();
-                        List<TaskView> taskViews = mSv.getTaskViews();
-                        for (int i = taskViews.size() - 1; i >= 0; i--) {
-                            layoutAlgorithm.addUnfocusedTaskOverride(taskViews.get(i).getTask(),
-                                    stackScroll);
-                        }
-                        layoutAlgorithm.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
-
-                        // Disallow parents from intercepting touch events
-                        final ViewParent parent = mSv.getParent();
-                        if (parent != null) {
-                            parent.requestDisallowInterceptTouchEvent(true);
-                        }
-
-                        MetricsLogger.action(mSv.getContext(), MetricsEvent.OVERVIEW_SCROLL);
-                        mLastY = mDownY = y;
-                    }
-                }
-                if (mIsScrolling) {
-                    // If we just move linearly on the screen, then that would map to 1/arclength
-                    // of the curve, so just move the scroll proportional to that
-                    float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
-
-                    // Modulate the overscroll to prevent users from pulling the stack too far
-                    float minScrollP = layoutAlgorithm.mMinScrollP;
-                    float maxScrollP = layoutAlgorithm.mMaxScrollP;
-                    float curScrollP = mDownScrollP + deltaP;
-                    if (curScrollP < minScrollP || curScrollP > maxScrollP) {
-                        float clampedScrollP = Utilities.clamp(curScrollP, minScrollP, maxScrollP);
-                        float overscrollP = (curScrollP - clampedScrollP);
-                        float maxOverscroll = LegacyRecentsImpl.getConfiguration().isLowRamDevice
-                                ? layoutAlgorithm.mTaskStackLowRamLayoutAlgorithm.getMaxOverscroll()
-                                : MAX_OVERSCROLL;
-                        float overscrollX = Math.abs(overscrollP) / maxOverscroll;
-                        float interpX = OVERSCROLL_INTERP.getInterpolation(overscrollX);
-                        curScrollP = clampedScrollP + Math.signum(overscrollP) *
-                                (interpX * maxOverscroll);
-                    }
-                    mDownScrollP += mScroller.setDeltaStackScroll(mDownScrollP,
-                            curScrollP - mDownScrollP);
-                    mStackViewScrolledEvent.updateY(y - mLastY);
-                    EventBus.getDefault().send(mStackViewScrolledEvent);
-                }
-
-                mLastY = y;
-                mVelocityTracker.addMovement(ev);
-                break;
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                int pointerIndex = ev.getActionIndex();
-                int pointerId = ev.getPointerId(pointerIndex);
-                if (pointerId == mActivePointerId) {
-                    // Select a new active pointer id and reset the motion state
-                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
-                    mActivePointerId = ev.getPointerId(newPointerIndex);
-                    mDownX = (int) ev.getX(pointerIndex);
-                    mDownY = (int) ev.getY(pointerIndex);
-                    mLastY = mDownY;
-                    mDownScrollP = mScroller.getStackScroll();
-                }
-                mVelocityTracker.addMovement(ev);
-                break;
-            }
-            case MotionEvent.ACTION_UP: {
-                mVelocityTracker.addMovement(ev);
-                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int activePointerIndex = ev.findPointerIndex(mActivePointerId);
-                int y = (int) ev.getY(activePointerIndex);
-                int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
-                if (mIsScrolling) {
-                    if (mScroller.isScrollOutOfBounds()) {
-                        mScroller.animateBoundScroll();
-                    } else if (Math.abs(velocity) > mMinimumVelocity &&
-                            !LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                        float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mMaxScrollP);
-                        float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
-                                layoutAlgorithm.mMinScrollP);
-                        mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
-                                mOverscrollSize);
-                        mSv.invalidate();
-                    }
-
-                    // Reset the focused task after the user has scrolled, but we have no scrolling
-                    // in grid layout and therefore we don't want to reset the focus there.
-                    if (!mSv.mTouchExplorationEnabled && !mSv.useGridLayout()) {
-                        if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                            mScroller.scrollToClosestTask(velocity);
-                        } else {
-                            mSv.resetFocusedTask(mSv.getFocusedTask());
-                        }
-                    }
-                } else if (mActiveTaskView == null) {
-                    // This tap didn't start on a task.
-                    maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY());
-                }
-
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mIsScrolling = false;
-                recycleVelocityTracker();
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL: {
-                mActivePointerId = INACTIVE_POINTER_ID;
-                mIsScrolling = false;
-                recycleVelocityTracker();
-                break;
-            }
-        }
-        return mIsScrolling;
-    }
-
-    /** Hides recents if the up event at (x, y) is a tap on the background area. */
-    void maybeHideRecentsFromBackgroundTap(int x, int y) {
-        // Ignore the up event if it's too far from its start position. The user might have been
-        // trying to scroll or swipe.
-        int dx = Math.abs(mDownX - x);
-        int dy = Math.abs(mDownY - y);
-        if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) {
-            return;
-        }
-
-        // Shift the tap position toward the center of the task stack and check to see if it would
-        // have hit a view. The user might have tried to tap on a task and missed slightly.
-        int shiftedX = x;
-        if (x > (mSv.getRight() - mSv.getLeft()) / 2) {
-            shiftedX -= mWindowTouchSlop;
-        } else {
-            shiftedX += mWindowTouchSlop;
-        }
-        if (findViewAtPoint(shiftedX, y) != null) {
-            return;
-        }
-
-        // Disallow tapping above and below the stack to dismiss recents
-        if (x > mSv.mLayoutAlgorithm.mStackRect.left && x < mSv.mLayoutAlgorithm.mStackRect.right) {
-            return;
-        }
-
-        // The user intentionally tapped on the background, which is like a tap on the "desktop".
-        // Hide recents and transition to the launcher.
-        EventBus.getDefault().send(new HideRecentsEvent(false, true));
-    }
-
-    /** Handles generic motion events */
-    public boolean onGenericMotionEvent(MotionEvent ev) {
-        if ((ev.getSource() & InputDevice.SOURCE_CLASS_POINTER) ==
-                InputDevice.SOURCE_CLASS_POINTER) {
-            int action = ev.getAction();
-            switch (action & MotionEvent.ACTION_MASK) {
-                case MotionEvent.ACTION_SCROLL:
-                    // Find the front most task and scroll the next task to the front
-                    float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
-                    if (vScroll > 0) {
-                        mSv.setRelativeFocusedTask(true, true /* stackTasksOnly */,
-                                false /* animated */);
-                    } else {
-                        mSv.setRelativeFocusedTask(false, true /* stackTasksOnly */,
-                                false /* animated */);
-                    }
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**** SwipeHelper Implementation ****/
-
-    @Override
-    public View getChildAtPosition(MotionEvent ev) {
-        TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY());
-        if (tv != null && canChildBeDismissed(tv)) {
-            return tv;
-        }
-        return null;
-    }
-
-    @Override
-    public boolean canChildBeDismissed(View v) {
-        // Disallow dismissing an already dismissed task
-        TaskView tv = (TaskView) v;
-        Task task = tv.getTask();
-        return !mSwipeHelperAnimations.containsKey(v) &&
-                (mSv.getStack().indexOfTask(task) != -1);
-    }
-
-    /**
-     * Starts a manual drag that goes through the same swipe helper path.
-     */
-    public void onBeginManualDrag(TaskView v) {
-        mActiveTaskView = v;
-        mSwipeHelperAnimations.put(v, null);
-        onBeginDrag(v);
-    }
-
-    @Override
-    public void onBeginDrag(View v) {
-        TaskView tv = (TaskView) v;
-
-        // Disable clipping with the stack while we are swiping
-        tv.setClipViewInStack(false);
-        // Disallow touch events from this task view
-        tv.setTouchEnabled(false);
-        // Disallow parents from intercepting touch events
-        final ViewParent parent = mSv.getParent();
-        if (parent != null) {
-            parent.requestDisallowInterceptTouchEvent(true);
-        }
-
-        // Add this task to the set of tasks we are deleting
-        mSv.addIgnoreTask(tv.getTask());
-
-        // Determine if we are animating the other tasks while dismissing this task
-        mCurrentTasks = new ArrayList<Task>(mSv.getStack().getTasks());
-        MutableBoolean isFrontMostTask = new MutableBoolean(false);
-        Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
-        TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
-        TaskStackViewScroller stackScroller = mSv.getScroller();
-        if (anchorTask != null) {
-            // Get the current set of task transforms
-            mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms);
-
-            // Get the stack scroll of the task to anchor to (since we are removing something, the
-            // front most task will be our anchor task)
-            float prevAnchorTaskScroll = 0;
-            boolean pullStackForward = mCurrentTasks.size() > 0;
-            if (pullStackForward) {
-                if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                    float index = layoutAlgorithm.getStackScrollForTask(anchorTask);
-                    prevAnchorTaskScroll = mSv.getStackAlgorithm().mTaskStackLowRamLayoutAlgorithm
-                            .getScrollPForTask((int) index);
-                } else {
-                    prevAnchorTaskScroll = layoutAlgorithm.getStackScrollForTask(anchorTask);
-                }
-            }
-
-            // Calculate where the views would be without the deleting tasks
-            mSv.updateLayoutAlgorithm(false /* boundScroll */);
-
-            float newStackScroll = stackScroller.getStackScroll();
-            if (isFrontMostTask.value) {
-                // Bound the stack scroll to pull tasks forward if necessary
-                newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll);
-            } else if (pullStackForward) {
-                // Otherwise, offset the scroll by the movement of the anchor task
-                float anchorTaskScroll =
-                        layoutAlgorithm.getStackScrollForTaskIgnoreOverrides(anchorTask);
-                if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                    float index = layoutAlgorithm.getStackScrollForTask(anchorTask);
-                    anchorTaskScroll = mSv.getStackAlgorithm().mTaskStackLowRamLayoutAlgorithm
-                            .getScrollPForTask((int) index);
-                }
-                float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll);
-                if (layoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED
-                        && !LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-                    // If we are focused, we don't want the front task to move, but otherwise, we
-                    // allow the back task to move up, and the front task to move back
-                    stackScrollOffset *= 0.75f;
-                }
-                newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll()
-                        + stackScrollOffset);
-            }
-
-            // Pick up the newly visible views, not including the deleting tasks
-            mSv.bindVisibleTaskViews(newStackScroll, true /* ignoreTaskOverrides */);
-
-            // Get the final set of task transforms (with task removed)
-            mSv.getLayoutTaskTransforms(newStackScroll, TaskStackLayoutAlgorithm.STATE_UNFOCUSED,
-                    mCurrentTasks, true /* ignoreTaskOverrides */, mFinalTaskTransforms);
-
-            // Set the target to scroll towards upon dismissal
-            mTargetStackScroll = newStackScroll;
-
-            /*
-             * Post condition: All views that will be visible as a part of the gesture are retrieved
-             *                 and at their initial positions.  The stack is still at the current
-             *                 scroll, but the layout is updated without the task currently being
-             *                 dismissed.  The final layout is in the unfocused stack state, which
-             *                 will be applied when the current task is dismissed.
-             */
-        }
-    }
-
-    @Override
-    public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) {
-        // Only update the swipe progress for the surrounding tasks if the dismiss animation was not
-        // preempted from a call to cancelNonDismissTaskAnimations
-        if ((mActiveTaskView == v || mSwipeHelperAnimations.containsKey(v)) &&
-                !LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            updateTaskViewTransforms(
-                    Interpolators.FAST_OUT_SLOW_IN.getInterpolation(swipeProgress));
-        }
-        return true;
-    }
-
-    /**
-     * Called after the {@link TaskView} is finished animating away.
-     */
-    @Override
-    public void onChildDismissed(View v) {
-        TaskView tv = (TaskView) v;
-
-        // Re-enable clipping with the stack (we will reuse this view)
-        tv.setClipViewInStack(true);
-        // Re-enable touch events from this task view
-        tv.setTouchEnabled(true);
-        // Update the scroll to the final scroll position before laying out the tasks during dismiss
-        if (mSwipeHelperAnimations.containsKey(v)) {
-            mSv.getScroller().setStackScroll(mTargetStackScroll, null);
-        }
-        // Remove the task view from the stack, ignoring the animation if we've started dragging
-        // again
-        EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv,
-                mSwipeHelperAnimations.containsKey(v)
-                    ? new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION,
-                        Interpolators.FAST_OUT_SLOW_IN)
-                    : null));
-        // Only update the final scroll and layout state (set in onBeginDrag()) if the dismiss
-        // animation was not preempted from a call to cancelNonDismissTaskAnimations
-        if (mSwipeHelperAnimations.containsKey(v)) {
-            // Update the focus state to the final focus state
-            mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
-            mSv.getStackAlgorithm().clearUnfocusedTaskOverrides();
-            // Stop tracking this deletion animation
-            mSwipeHelperAnimations.remove(v);
-        }
-        // Keep track of deletions by keyboard
-        MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source",
-                Constants.Metrics.DismissSourceSwipeGesture);
-    }
-
-    /**
-     * Called after the {@link TaskView} is finished animating back into the list.
-     * onChildDismissed() calls.
-     */
-    @Override
-    public void onChildSnappedBack(View v, float targetLeft) {
-        TaskView tv = (TaskView) v;
-
-        // Re-enable clipping with the stack
-        tv.setClipViewInStack(true);
-        // Re-enable touch events from this task view
-        tv.setTouchEnabled(true);
-
-        // Stop tracking this deleting task, and update the layout to include this task again.  The
-        // stack scroll does not need to be reset, since the scroll has not actually changed in
-        // onBeginDrag().
-        mSv.removeIgnoreTask(tv.getTask());
-        mSv.updateLayoutAlgorithm(false /* boundScroll */);
-        mSv.relayoutTaskViews(AnimationProps.IMMEDIATE);
-        mSwipeHelperAnimations.remove(v);
-    }
-
-    @Override
-    public void onDragCancelled(View v) {
-        // Do nothing
-    }
-
-    @Override
-    public boolean isAntiFalsingNeeded() {
-        return false;
-    }
-
-    @Override
-    public float getFalsingThresholdFactor() {
-        return 0;
-    }
-
-    /**
-     * Interpolates the non-deleting tasks to their final transforms from their current transforms.
-     */
-    private void updateTaskViewTransforms(float dismissFraction) {
-        List<TaskView> taskViews = mSv.getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-
-            if (mSv.isIgnoredTask(task)) {
-                continue;
-            }
-
-            int taskIndex = mCurrentTasks.indexOf(task);
-            if (taskIndex == -1) {
-                // If a task was added to the stack view after the start of the dismiss gesture,
-                // just ignore it
-                continue;
-            }
-
-            TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex);
-            TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex);
-
-            mTmpTransform.copyFrom(fromTransform);
-            // We only really need to interpolate the bounds, progress and translation
-            mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction,
-                    fromTransform.rect, toTransform.rect));
-            mTmpTransform.dimAlpha = fromTransform.dimAlpha + (toTransform.dimAlpha -
-                    fromTransform.dimAlpha) * dismissFraction;
-            mTmpTransform.viewOutlineAlpha = fromTransform.viewOutlineAlpha +
-                    (toTransform.viewOutlineAlpha - fromTransform.viewOutlineAlpha) *
-                            dismissFraction;
-            mTmpTransform.translationZ = fromTransform.translationZ +
-                    (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
-
-            mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
-        }
-    }
-
-    /** Returns the view at the specified coordinates */
-    private TaskView findViewAtPoint(int x, int y) {
-        List<Task> tasks = mSv.getStack().getTasks();
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            TaskView tv = mSv.getChildViewForTask(tasks.get(i));
-            if (tv != null && tv.getVisibility() == View.VISIBLE) {
-                if (mSv.isTouchPointInView(x, y, tv)) {
-                    return tv;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the scaled size used to calculate the dismiss fraction.
-     */
-    public float getScaledDismissSize() {
-        return 1.5f * Math.max(mSv.getWidth(), mSv.getHeight());
-    }
-
-    /**
-     * Returns whether swiping is enabled.
-     */
-    private boolean isSwipingEnabled() {
-        return !mSv.useGridLayout();
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskView.java
deleted file mode 100644
index ab0bf95..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskView.java
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Outline;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.util.Property;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewOutlineProvider;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
-import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed
- * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down
- * the view hierarchy, but not upwards from any of its children (the TaskView will relayout itself
- * with the previous bounds if any child requests layout).
- */
-public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks,
-        TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener {
-
-    /** The TaskView callbacks */
-    interface TaskViewCallbacks {
-        void onTaskViewClipStateChanged(TaskView tv);
-    }
-
-    /**
-     * The dim overlay is generally calculated from the task progress, but occasionally (like when
-     * launching) needs to be animated independently of the task progress.  This call is only used
-     * when animating the task into Recents, when the header dim is already applied
-     */
-    public static final Property<TaskView, Float> DIM_ALPHA_WITHOUT_HEADER =
-            new FloatProperty<TaskView>("dimAlphaWithoutHeader") {
-                @Override
-                public void setValue(TaskView tv, float dimAlpha) {
-                    tv.setDimAlphaWithoutHeader(dimAlpha);
-                }
-
-                @Override
-                public Float get(TaskView tv) {
-                    return tv.getDimAlpha();
-                }
-            };
-
-    /**
-     * The dim overlay is generally calculated from the task progress, but occasionally (like when
-     * launching) needs to be animated independently of the task progress.
-     */
-    public static final Property<TaskView, Float> DIM_ALPHA =
-            new FloatProperty<TaskView>("dimAlpha") {
-                @Override
-                public void setValue(TaskView tv, float dimAlpha) {
-                    tv.setDimAlpha(dimAlpha);
-                }
-
-                @Override
-                public Float get(TaskView tv) {
-                    return tv.getDimAlpha();
-                }
-            };
-
-    /**
-     * The dim overlay is generally calculated from the task progress, but occasionally (like when
-     * launching) needs to be animated independently of the task progress.
-     */
-    public static final Property<TaskView, Float> VIEW_OUTLINE_ALPHA =
-            new FloatProperty<TaskView>("viewOutlineAlpha") {
-                @Override
-                public void setValue(TaskView tv, float alpha) {
-                    tv.getViewBounds().setAlpha(alpha);
-                }
-
-                @Override
-                public Float get(TaskView tv) {
-                    return tv.getViewBounds().getAlpha();
-                }
-            };
-
-    @ViewDebug.ExportedProperty(category="recents")
-    private float mDimAlpha;
-    private float mActionButtonTranslationZ;
-
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="task_")
-    private Task mTask;
-    private boolean mTaskBound;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mClipViewInStack = true;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mTouchExplorationEnabled;
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mIsDisabledInSafeMode;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="view_bounds_")
-    private AnimateableViewBounds mViewBounds;
-
-    private AnimatorSet mTransformAnimation;
-    private ObjectAnimator mDimAnimator;
-    private ObjectAnimator mOutlineAnimator;
-    private final TaskViewTransform mTargetAnimationTransform = new TaskViewTransform();
-    private ArrayList<Animator> mTmpAnimators = new ArrayList<>();
-
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_")
-    protected TaskViewThumbnail mThumbnailView;
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
-    protected TaskViewHeader mHeaderView;
-    private View mActionButtonView;
-    private View mIncompatibleAppToastView;
-    private TaskViewCallbacks mCb;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    private Point mDownTouchPos = new Point();
-
-    private Toast mDisabledAppToast;
-
-    public TaskView(Context context) {
-        this(context, null);
-    }
-
-    public TaskView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        Resources res = context.getResources();
-        mViewBounds = createOutlineProvider();
-        if (config.fakeShadows) {
-            setBackground(new FakeShadowDrawable(res, config));
-        }
-        setOutlineProvider(mViewBounds);
-        setOnLongClickListener(this);
-        setAccessibilityDelegate(new TaskViewAccessibilityDelegate(this));
-    }
-
-    /** Set callback */
-    void setCallbacks(TaskViewCallbacks cb) {
-        mCb = cb;
-    }
-
-    /**
-     * Called from RecentsActivity when it is relaunched.
-     */
-    void onReload(boolean isResumingFromVisible) {
-        resetNoUserInteractionState();
-        if (!isResumingFromVisible) {
-            resetViewProperties();
-        }
-    }
-
-    /** Gets the task */
-    public Task getTask() {
-        return mTask;
-    }
-
-    /* Create an outline provider to clip and outline the view */
-    protected AnimateableViewBounds createOutlineProvider() {
-        return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize(
-            R.dimen.recents_task_view_shadow_rounded_corners_radius));
-    }
-
-    /** Returns the view bounds. */
-    AnimateableViewBounds getViewBounds() {
-        return mViewBounds;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        // Bind the views
-        mHeaderView = findViewById(R.id.task_view_bar);
-        mThumbnailView = findViewById(R.id.task_view_thumbnail);
-        mThumbnailView.updateClipToTaskBar(mHeaderView);
-        mActionButtonView = findViewById(R.id.lock_to_app_fab);
-        mActionButtonView.setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                // Set the outline to match the FAB background
-                outline.setOval(0, 0, mActionButtonView.getWidth(), mActionButtonView.getHeight());
-                outline.setAlpha(0.35f);
-            }
-        });
-        mActionButtonView.setOnClickListener(this);
-        mActionButtonTranslationZ = mActionButtonView.getTranslationZ();
-    }
-
-    /**
-     * Update the task view when the configuration changes.
-     */
-    protected void onConfigurationChanged() {
-        mHeaderView.onConfigurationChanged();
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        if (w > 0 && h > 0) {
-            mHeaderView.onTaskViewSizeChanged(w, h);
-            mThumbnailView.onTaskViewSizeChanged(w, h);
-
-            mActionButtonView.setTranslationX(w - getMeasuredWidth());
-            mActionButtonView.setTranslationY(h - getMeasuredHeight());
-        }
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    @Override
-    protected void measureContents(int width, int height) {
-        int widthWithoutPadding = width - mPaddingLeft - mPaddingRight;
-        int heightWithoutPadding = height - mPaddingTop - mPaddingBottom;
-        int widthSpec = MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY);
-        int heightSpec = MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY);
-
-        // Measure the content
-        measureChildren(widthSpec, heightSpec);
-
-        setMeasuredDimension(width, height);
-    }
-
-    void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
-            AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
-        RecentsConfiguration config = LegacyRecentsImpl.getConfiguration();
-        cancelTransformAnimation();
-
-        // Compose the animations for the transform
-        mTmpAnimators.clear();
-        toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
-        if (toAnimation.isImmediate()) {
-            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
-                setDimAlpha(toTransform.dimAlpha);
-            }
-            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
-                mViewBounds.setAlpha(toTransform.viewOutlineAlpha);
-            }
-            // Manually call back to the animator listener and update callback
-            if (toAnimation.getListener() != null) {
-                toAnimation.getListener().onAnimationEnd(null);
-            }
-            if (updateCallback != null) {
-                updateCallback.onAnimationUpdate(null);
-            }
-        } else {
-            // Both the progress and the update are a function of the bounds movement of the task
-            if (Float.compare(getDimAlpha(), toTransform.dimAlpha) != 0) {
-                mDimAnimator = ObjectAnimator.ofFloat(this, DIM_ALPHA, getDimAlpha(),
-                        toTransform.dimAlpha);
-                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mDimAnimator));
-            }
-            if (Float.compare(mViewBounds.getAlpha(), toTransform.viewOutlineAlpha) != 0) {
-                mOutlineAnimator = ObjectAnimator.ofFloat(this, VIEW_OUTLINE_ALPHA,
-                        mViewBounds.getAlpha(), toTransform.viewOutlineAlpha);
-                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, mOutlineAnimator));
-            }
-            if (updateCallback != null) {
-                ValueAnimator updateCallbackAnim = ValueAnimator.ofInt(0, 1);
-                updateCallbackAnim.addUpdateListener(updateCallback);
-                mTmpAnimators.add(toAnimation.apply(AnimationProps.BOUNDS, updateCallbackAnim));
-            }
-
-            // Create the animator
-            mTransformAnimation = toAnimation.createAnimator(mTmpAnimators);
-            mTransformAnimation.start();
-            mTargetAnimationTransform.copyFrom(toTransform);
-        }
-    }
-
-    /** Resets this view's properties */
-    void resetViewProperties() {
-        cancelTransformAnimation();
-        setDimAlpha(0);
-        setVisibility(View.VISIBLE);
-        getViewBounds().reset();
-        getHeaderView().reset();
-        TaskViewTransform.reset(this);
-
-        mActionButtonView.setScaleX(1f);
-        mActionButtonView.setScaleY(1f);
-        mActionButtonView.setAlpha(0f);
-        mActionButtonView.setTranslationX(0f);
-        mActionButtonView.setTranslationY(0f);
-        mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
-        if (mIncompatibleAppToastView != null) {
-            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    /**
-     * @return whether we are animating towards {@param transform}
-     */
-    boolean isAnimatingTo(TaskViewTransform transform) {
-        return mTransformAnimation != null && mTransformAnimation.isStarted()
-                && mTargetAnimationTransform.isSame(transform);
-    }
-
-    /**
-     * Cancels any current transform animations.
-     */
-    public void cancelTransformAnimation() {
-        cancelDimAnimationIfExists();
-        Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
-        Utilities.cancelAnimationWithoutCallbacks(mOutlineAnimator);
-    }
-
-    private void cancelDimAnimationIfExists() {
-        if (mDimAnimator != null) {
-            mDimAnimator.cancel();
-        }
-    }
-
-    /** Enables/disables handling touch on this task view. */
-    public void setTouchEnabled(boolean enabled) {
-        setOnClickListener(enabled ? this : null);
-    }
-
-    /** Animates this task view if the user does not interact with the stack after a certain time. */
-    public void startNoUserInteractionAnimation() {
-        mHeaderView.startNoUserInteractionAnimation();
-    }
-
-    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
-    void setNoUserInteractionState() {
-        mHeaderView.setNoUserInteractionState();
-    }
-
-    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
-    void resetNoUserInteractionState() {
-        mHeaderView.resetNoUserInteractionState();
-    }
-
-    /** Dismisses this task. */
-    void dismissTask() {
-        // Animate out the view and call the callback
-        final TaskView tv = this;
-        DismissTaskViewEvent dismissEvent = new DismissTaskViewEvent(tv);
-        dismissEvent.addPostAnimationCallback(new Runnable() {
-            @Override
-            public void run() {
-                EventBus.getDefault().send(new TaskViewDismissedEvent(mTask, tv,
-                        new AnimationProps(TaskStackView.DEFAULT_SYNC_STACK_DURATION,
-                                Interpolators.FAST_OUT_SLOW_IN)));
-            }
-        });
-        EventBus.getDefault().send(dismissEvent);
-    }
-
-    /**
-     * Returns whether this view should be clipped, or any views below should clip against this
-     * view.
-     */
-    boolean shouldClipViewInStack() {
-        if (getVisibility() != View.VISIBLE || LegacyRecentsImpl.getConfiguration().isLowRamDevice) {
-            return false;
-        }
-        return mClipViewInStack;
-    }
-
-    /** Sets whether this view should be clipped, or clipped against. */
-    void setClipViewInStack(boolean clip) {
-        if (clip != mClipViewInStack) {
-            mClipViewInStack = clip;
-            if (mCb != null) {
-                mCb.onTaskViewClipStateChanged(this);
-            }
-        }
-    }
-
-    public TaskViewHeader getHeaderView() {
-        return mHeaderView;
-    }
-
-    /**
-     * Sets the current dim.
-     */
-    public void setDimAlpha(float dimAlpha) {
-        mDimAlpha = dimAlpha;
-        mThumbnailView.setDimAlpha(dimAlpha);
-        mHeaderView.setDimAlpha(dimAlpha);
-    }
-
-    /**
-     * Sets the current dim without updating the header's dim.
-     */
-    public void setDimAlphaWithoutHeader(float dimAlpha) {
-        mDimAlpha = dimAlpha;
-        mThumbnailView.setDimAlpha(dimAlpha);
-    }
-
-    /**
-     * Returns the current dim.
-     */
-    public float getDimAlpha() {
-        return mDimAlpha;
-    }
-
-    /**
-     * Explicitly sets the focused state of this task.
-     */
-    public void setFocusedState(boolean isFocused, boolean requestViewFocus) {
-        if (isFocused) {
-            if (requestViewFocus && !isFocused()) {
-                requestFocus();
-            }
-        } else {
-            if (isAccessibilityFocused() && mTouchExplorationEnabled) {
-                clearAccessibilityFocus();
-            }
-        }
-    }
-
-    /**
-     * Shows the action button.
-     * @param fadeIn whether or not to animate the action button in.
-     * @param fadeInDuration the duration of the action button animation, only used if
-     *                       {@param fadeIn} is true.
-     */
-    public void showActionButton(boolean fadeIn, int fadeInDuration) {
-        mActionButtonView.setVisibility(View.VISIBLE);
-
-        if (fadeIn && mActionButtonView.getAlpha() < 1f) {
-            mActionButtonView.animate()
-                    .alpha(1f)
-                    .scaleX(1f)
-                    .scaleY(1f)
-                    .setDuration(fadeInDuration)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .start();
-        } else {
-            mActionButtonView.setScaleX(1f);
-            mActionButtonView.setScaleY(1f);
-            mActionButtonView.setAlpha(1f);
-            mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
-        }
-    }
-
-    /**
-     * Immediately hides the action button.
-     *
-     * @param fadeOut whether or not to animate the action button out.
-     */
-    public void hideActionButton(boolean fadeOut, int fadeOutDuration, boolean scaleDown,
-            final Animator.AnimatorListener animListener) {
-        if (fadeOut && mActionButtonView.getAlpha() > 0f) {
-            if (scaleDown) {
-                float toScale = 0.9f;
-                mActionButtonView.animate()
-                        .scaleX(toScale)
-                        .scaleY(toScale);
-            }
-            mActionButtonView.animate()
-                    .alpha(0f)
-                    .setDuration(fadeOutDuration)
-                    .setInterpolator(Interpolators.ALPHA_OUT)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            if (animListener != null) {
-                                animListener.onAnimationEnd(null);
-                            }
-                            mActionButtonView.setVisibility(View.INVISIBLE);
-                        }
-                    })
-                    .start();
-        } else {
-            mActionButtonView.setAlpha(0f);
-            mActionButtonView.setVisibility(View.INVISIBLE);
-            if (animListener != null) {
-                animListener.onAnimationEnd(null);
-            }
-        }
-    }
-
-    /**** TaskStackAnimationHelper.Callbacks Implementation ****/
-
-    @Override
-    public void onPrepareLaunchTargetForEnterAnimation() {
-        // These values will be animated in when onStartLaunchTargetEnterAnimation() is called
-        setDimAlphaWithoutHeader(0);
-        mActionButtonView.setAlpha(0f);
-        if (mIncompatibleAppToastView != null &&
-                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
-            mIncompatibleAppToastView.setAlpha(0f);
-        }
-    }
-
-    @Override
-    public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
-            boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
-        cancelDimAnimationIfExists();
-
-        // Dim the view after the app window transitions down into recents
-        postAnimationTrigger.increment();
-        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
-        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
-                DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha));
-        mDimAnimator.addListener(postAnimationTrigger.decrementOnAnimationEnd());
-        mDimAnimator.start();
-
-        if (screenPinningEnabled) {
-            showActionButton(true /* fadeIn */, duration /* fadeInDuration */);
-        }
-
-        if (mIncompatibleAppToastView != null &&
-                mIncompatibleAppToastView.getVisibility() == View.VISIBLE) {
-            mIncompatibleAppToastView.animate()
-                    .alpha(1f)
-                    .setDuration(duration)
-                    .setInterpolator(Interpolators.ALPHA_IN)
-                    .start();
-        }
-    }
-
-    @Override
-    public void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
-            ReferenceCountedTrigger postAnimationTrigger) {
-        Utilities.cancelAnimationWithoutCallbacks(mDimAnimator);
-
-        // Un-dim the view before/while launching the target
-        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
-        mDimAnimator = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
-                DIM_ALPHA, getDimAlpha(), 0));
-        mDimAnimator.start();
-
-        postAnimationTrigger.increment();
-        hideActionButton(true /* fadeOut */, duration,
-                !screenPinningRequested /* scaleDown */,
-                postAnimationTrigger.decrementOnAnimationEnd());
-    }
-
-    @Override
-    public void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled) {
-        if (screenPinningEnabled) {
-            showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
-        }
-    }
-
-    /**** TaskCallbacks Implementation ****/
-
-    public void onTaskBound(Task t, boolean touchExplorationEnabled, int displayOrientation,
-            Rect displayRect) {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        mTouchExplorationEnabled = touchExplorationEnabled;
-        mTask = t;
-        mTaskBound = true;
-        mTask.addCallback(this);
-        mIsDisabledInSafeMode = !mTask.isSystemApp && ssp.isInSafeMode();
-        mThumbnailView.bindToTask(mTask, mIsDisabledInSafeMode, displayOrientation, displayRect);
-        mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
-
-        if (!t.isDockable && ssp.hasDockedTask()) {
-            if (mIncompatibleAppToastView == null) {
-                mIncompatibleAppToastView = Utilities.findViewStubById(this,
-                        R.id.incompatible_app_toast_stub).inflate();
-                TextView msg = findViewById(com.android.internal.R.id.message);
-                msg.setText(R.string.dock_non_resizeble_failed_to_dock_text);
-            }
-            mIncompatibleAppToastView.setVisibility(View.VISIBLE);
-        } else if (mIncompatibleAppToastView != null) {
-            mIncompatibleAppToastView.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    @Override
-    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
-        if (mTaskBound) {
-            // Update each of the views to the new task data
-            mThumbnailView.onTaskDataLoaded(thumbnailData);
-            mHeaderView.onTaskDataLoaded();
-        }
-    }
-
-    @Override
-    public void onTaskDataUnloaded() {
-        // Unbind each of the views from the task and remove the task callback
-        mTask.removeCallback(this);
-        mThumbnailView.unbindFromTask();
-        mHeaderView.unbindFromTask(mTouchExplorationEnabled);
-        mTaskBound = false;
-    }
-
-    @Override
-    public void onTaskWindowingModeChanged() {
-        // Force rebind the header, the thumbnail does not change due to stack changes
-        mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
-        mHeaderView.onTaskDataLoaded();
-    }
-
-    /**** View.OnClickListener Implementation ****/
-
-    @Override
-     public void onClick(final View v) {
-        if (mIsDisabledInSafeMode) {
-            Context context = getContext();
-            String msg = context.getString(R.string.recents_launch_disabled_message, mTask.title);
-            if (mDisabledAppToast != null) {
-                mDisabledAppToast.cancel();
-            }
-            mDisabledAppToast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
-            mDisabledAppToast.show();
-            return;
-        }
-
-        boolean screenPinningRequested = false;
-        if (v == mActionButtonView) {
-            // Reset the translation of the action button before we animate it out
-            mActionButtonView.setTranslationZ(0f);
-            screenPinningRequested = true;
-        }
-        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested));
-
-        MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
-                mTask.key.getComponent().toString());
-    }
-
-    /**** View.OnLongClickListener Implementation ****/
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (!LegacyRecentsImpl.getConfiguration().dragToSplitEnabled) {
-            return false;
-        }
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        boolean inBounds = false;
-        Rect clipBounds = new Rect(mViewBounds.getClipBounds());
-        if (!clipBounds.isEmpty()) {
-            // If we are clipping the view to the bounds, manually do the hit test.
-            clipBounds.scale(getScaleX());
-            inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
-        } else {
-            // Otherwise just make sure we're within the view's bounds.
-            inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight();
-        }
-        if (v == this && inBounds && !ssp.hasDockedTask()) {
-            // Start listening for drag events
-            setClipViewInStack(false);
-
-            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
-            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
-
-            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
-            return true;
-        }
-        return false;
-    }
-
-    /**** Events ****/
-
-    public final void onBusEvent(DragEndEvent event) {
-        if (!(event.dropTarget instanceof DockState)) {
-            event.addPostAnimationCallback(() -> {
-                // Reset the clip state for the drag view after the end animation completes
-                setClipViewInStack(true);
-            });
-        }
-        EventBus.getDefault().unregister(this);
-    }
-
-    public final void onBusEvent(DragEndCancelledEvent event) {
-        // Reset the clip state for the drag view after the cancel animation completes
-        event.addPostAnimationCallback(() -> {
-            setClipViewInStack(true);
-        });
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print("TaskView");
-        writer.print(" mTask="); writer.print(mTask.key.id);
-        writer.println();
-
-        mThumbnailView.dump(innerPrefix, writer);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
deleted file mode 100644
index 7bcad75..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ /dev/null
@@ -1,94 +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.recents.views;
-
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-
-public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
-    private static final String TAG = "TaskViewAccessibilityDelegate";
-
-    private final TaskView mTaskView;
-
-    protected static final int SPLIT_TASK_TOP = R.id.action_split_task_to_top;
-    protected static final int SPLIT_TASK_LEFT = R.id.action_split_task_to_left;
-    protected static final int SPLIT_TASK_RIGHT = R.id.action_split_task_to_right;
-
-    protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
-
-    public TaskViewAccessibilityDelegate(TaskView taskView) {
-        mTaskView = taskView;
-        Context context = taskView.getContext();
-        mActions.put(SPLIT_TASK_TOP, new AccessibilityAction(SPLIT_TASK_TOP,
-                context.getString(R.string.recents_accessibility_split_screen_top)));
-        mActions.put(SPLIT_TASK_LEFT, new AccessibilityAction(SPLIT_TASK_LEFT,
-                context.getString(R.string.recents_accessibility_split_screen_left)));
-        mActions.put(SPLIT_TASK_RIGHT, new AccessibilityAction(SPLIT_TASK_RIGHT,
-                context.getString(R.string.recents_accessibility_split_screen_right)));
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(host, info);
-        if (ActivityTaskManager.supportsSplitScreenMultiWindow(mTaskView.getContext())
-                && !LegacyRecentsImpl.getSystemServices().hasDockedTask()) {
-            DockState[] dockStates = LegacyRecentsImpl.getConfiguration()
-                    .getDockStatesForCurrentOrientation();
-            for (DockState dockState: dockStates) {
-                if (dockState == DockState.TOP) {
-                    info.addAction(mActions.get(SPLIT_TASK_TOP));
-                } else if (dockState == DockState.LEFT) {
-                    info.addAction(mActions.get(SPLIT_TASK_LEFT));
-                } else if (dockState == DockState.RIGHT) {
-                    info.addAction(mActions.get(SPLIT_TASK_RIGHT));
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        if (action == SPLIT_TASK_TOP) {
-            simulateDragIntoMultiwindow(DockState.TOP);
-        } else if (action == SPLIT_TASK_LEFT) {
-            simulateDragIntoMultiwindow(DockState.LEFT);
-        } else if (action == SPLIT_TASK_RIGHT) {
-            simulateDragIntoMultiwindow(DockState.RIGHT);
-        } else {
-            return super.performAccessibilityAction(host, action, args);
-        }
-        return true;
-    }
-
-    /** Simulate a user drag event to split the screen to the respected side */
-    private void simulateDragIntoMultiwindow(DockState dockState) {
-        EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
-                new Point(0,0), false /* isUserTouchInitiated */));
-        EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewHeader.java
deleted file mode 100644
index 21c0234..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
-import android.os.CountDownTimer;
-import androidx.core.graphics.ColorUtils;
-import android.util.AttributeSet;
-import android.util.IconDrawableFactory;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-
-/* The task bar view */
-public class TaskViewHeader extends FrameLayout
-        implements View.OnClickListener, View.OnLongClickListener {
-
-    private static IconDrawableFactory sDrawableFactory;
-
-    private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f;
-    private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
-    private static final int OVERLAY_REVEAL_DURATION = 250;
-    private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
-
-    /**
-     * A color drawable that draws a slight highlight at the top to help it stand out.
-     */
-    private class HighlightColorDrawable extends Drawable {
-
-        private Paint mHighlightPaint = new Paint();
-        private Paint mBackgroundPaint = new Paint();
-        private int mColor;
-        private float mDimAlpha;
-
-        public HighlightColorDrawable() {
-            mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
-            mBackgroundPaint.setAntiAlias(true);
-            mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
-            mHighlightPaint.setAntiAlias(true);
-        }
-
-        public void setColorAndDim(int color, float dimAlpha) {
-            if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) {
-                mColor = color;
-                mDimAlpha = dimAlpha;
-                if (mShouldDarkenBackgroundColor) {
-                    color = getSecondaryColor(color, false /* useLightOverlayColor */);
-                }
-                mBackgroundPaint.setColor(color);
-
-                ColorUtils.colorToHSL(color, mTmpHSL);
-                // TODO: Consider using the saturation of the color to adjust the lightness as well
-                mTmpHSL[2] = Math.min(1f,
-                        mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
-                mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
-
-                invalidateSelf();
-            }
-        }
-
-        @Override
-        public void setColorFilter(@Nullable ColorFilter colorFilter) {
-            // Do nothing
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-            // Do nothing
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            // Draw the highlight at the top edge (but put the bottom edge just out of view)
-            canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
-                    2 * Math.max(mHighlightHeight, mCornerRadius),
-                    mCornerRadius, mCornerRadius, mHighlightPaint);
-
-            // Draw the background with the rounded corners
-            canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
-                    getHeight() + mCornerRadius,
-                    mCornerRadius, mCornerRadius, mBackgroundPaint);
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.OPAQUE;
-        }
-
-        public int getColor() {
-            return mColor;
-        }
-    }
-
-    Task mTask;
-
-    // Header views
-    ImageView mIconView;
-    TextView mTitleView;
-    ImageView mMoveTaskButton;
-    ImageView mDismissButton;
-    FrameLayout mAppOverlayView;
-    ImageView mAppIconView;
-    ImageView mAppInfoView;
-    TextView mAppTitleView;
-    ProgressBar mFocusTimerIndicator;
-
-    // Header drawables
-    @ViewDebug.ExportedProperty(category="recents")
-    Rect mTaskViewRect = new Rect();
-    int mHeaderBarHeight;
-    int mHeaderButtonPadding;
-    int mCornerRadius;
-    int mHighlightHeight;
-    @ViewDebug.ExportedProperty(category="recents")
-    float mDimAlpha;
-    Drawable mLightDismissDrawable;
-    Drawable mDarkDismissDrawable;
-    Drawable mLightFullscreenIcon;
-    Drawable mDarkFullscreenIcon;
-    Drawable mLightInfoIcon;
-    Drawable mDarkInfoIcon;
-    int mTaskBarViewLightTextColor;
-    int mTaskBarViewDarkTextColor;
-    int mDisabledTaskBarBackgroundColor;
-    String mDismissDescFormat;
-    String mAppInfoDescFormat;
-    int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
-
-    // Header background
-    private HighlightColorDrawable mBackground;
-    private HighlightColorDrawable mOverlayBackground;
-    private float[] mTmpHSL = new float[3];
-
-    // Header dim, which is only used when task view hardware layers are not used
-    private Paint mDimLayerPaint = new Paint();
-
-    // Whether the background color should be darkened to differentiate from the primary color.
-    // Used in grid layout.
-    private boolean mShouldDarkenBackgroundColor = false;
-
-    private CountDownTimer mFocusTimerCountDown;
-
-    public TaskViewHeader(Context context) {
-        this(context, null);
-    }
-
-    public TaskViewHeader(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        setWillNotDraw(false);
-
-        // Load the dismiss resources
-        Resources res = context.getResources();
-        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
-        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
-        mCornerRadius = LegacyRecentsImpl.getConfiguration().isGridEnabled ?
-                res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
-                res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
-        mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
-        mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
-        mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
-        mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
-        mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
-        mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
-        mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
-        mDisabledTaskBarBackgroundColor =
-                context.getColor(R.color.recents_task_bar_disabled_background_color);
-        mDismissDescFormat = mContext.getString(
-                R.string.accessibility_recents_item_will_be_dismissed);
-        mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info);
-
-        // Configure the background and dim
-        mBackground = new HighlightColorDrawable();
-        mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
-        setBackground(mBackground);
-        mOverlayBackground = new HighlightColorDrawable();
-        mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
-        mDimLayerPaint.setAntiAlias(true);
-    }
-
-    /**
-     * Resets this header along with the TaskView.
-     */
-    public void reset() {
-        hideAppOverlay(true /* immediate */);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-
-        // Initialize the icon and description views
-        mIconView = findViewById(R.id.icon);
-        mIconView.setOnLongClickListener(this);
-        mTitleView = findViewById(R.id.title);
-        mDismissButton = findViewById(R.id.dismiss_task);
-
-        onConfigurationChanged();
-    }
-
-    /**
-     * Programmatically sets the layout params for a header bar layout.  This is necessary because
-     * we can't get resources based on the current configuration, but instead need to get them
-     * based on the device configuration.
-     */
-    private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
-        setLayoutParams(lp);
-        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
-        icon.setLayoutParams(lp);
-        lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
-        lp.setMarginStart(mHeaderBarHeight);
-        lp.setMarginEnd(mMoveTaskButton != null
-                ? 2 * mHeaderBarHeight
-                : mHeaderBarHeight);
-        title.setLayoutParams(lp);
-        if (secondaryButton != null) {
-            lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
-            lp.setMarginEnd(mHeaderBarHeight);
-            secondaryButton.setLayoutParams(lp);
-            secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
-                    mHeaderButtonPadding, mHeaderButtonPadding);
-        }
-        lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
-        button.setLayoutParams(lp);
-        button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
-                mHeaderButtonPadding);
-    }
-
-    /**
-     * Update the header view when the configuration changes.
-     */
-    public void onConfigurationChanged() {
-        // Update the dimensions of everything in the header. We do this because we need to use
-        // resources for the display, and not the current configuration.
-        Resources res = getResources();
-        int headerBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_task_view_header_height,
-                R.dimen.recents_task_view_header_height_tablet_land,
-                R.dimen.recents_grid_task_view_header_height);
-        int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
-                R.dimen.recents_task_view_header_button_padding,
-                R.dimen.recents_task_view_header_button_padding,
-                R.dimen.recents_task_view_header_button_padding,
-                R.dimen.recents_task_view_header_button_padding_tablet_land,
-                R.dimen.recents_task_view_header_button_padding,
-                R.dimen.recents_task_view_header_button_padding_tablet_land,
-                R.dimen.recents_grid_task_view_header_button_padding);
-        if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
-            mHeaderBarHeight = headerBarHeight;
-            mHeaderButtonPadding = headerButtonPadding;
-            updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton);
-            if (mAppOverlayView != null) {
-                updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
-            }
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        // Since we update the position of children based on the width of the parent and this view
-        // recompute these changes with the new view size
-        onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
-    }
-
-    /**
-     * Called when the task view frame changes, allowing us to move the contents of the header
-     * to match the frame changes.
-     */
-    public void onTaskViewSizeChanged(int width, int height) {
-        mTaskViewRect.set(0, 0, width, height);
-
-        boolean showTitle = true;
-        boolean showMoveIcon = true;
-        boolean showDismissIcon = true;
-        int rightInset = width - getMeasuredWidth();
-
-        mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
-        if (mMoveTaskButton != null) {
-            mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
-            mMoveTaskButton.setTranslationX(rightInset);
-        }
-        mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
-        mDismissButton.setTranslationX(rightInset);
-
-        setLeftTopRightBottom(0, 0, width, getMeasuredHeight());
-    }
-
-    @Override
-    public void onDrawForeground(Canvas canvas) {
-        super.onDrawForeground(canvas);
-
-        // Draw the dim layer with the rounded corners
-        canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
-                mCornerRadius, mCornerRadius, mDimLayerPaint);
-    }
-
-    /** Starts the focus timer. */
-    public void startFocusTimerIndicator(int duration) {
-        if (mFocusTimerIndicator == null) {
-            return;
-        }
-
-        mFocusTimerIndicator.setVisibility(View.VISIBLE);
-        mFocusTimerIndicator.setMax(duration);
-        mFocusTimerIndicator.setProgress(duration);
-        if (mFocusTimerCountDown != null) {
-            mFocusTimerCountDown.cancel();
-        }
-        mFocusTimerCountDown = new CountDownTimer(duration,
-                FOCUS_INDICATOR_INTERVAL_MS) {
-            public void onTick(long millisUntilFinished) {
-                mFocusTimerIndicator.setProgress((int) millisUntilFinished);
-            }
-
-            public void onFinish() {
-                // Do nothing
-            }
-        }.start();
-    }
-
-    /** Cancels the focus timer. */
-    public void cancelFocusTimerIndicator() {
-        if (mFocusTimerIndicator == null) {
-            return;
-        }
-
-        if (mFocusTimerCountDown != null) {
-            mFocusTimerCountDown.cancel();
-            mFocusTimerIndicator.setProgress(0);
-            mFocusTimerIndicator.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    /** Only exposed for the workaround for b/27815919. */
-    public ImageView getIconView() {
-        return mIconView;
-    }
-
-    /** Returns the secondary color for a primary color. */
-    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
-        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
-        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
-    }
-
-    /**
-     * Sets the dim alpha, only used when we are not using hardware layers.
-     * (see RecentsConfiguration.useHardwareLayers)
-     */
-    public void setDimAlpha(float dimAlpha) {
-        if (Float.compare(mDimAlpha, dimAlpha) != 0) {
-            mDimAlpha = dimAlpha;
-            mTitleView.setAlpha(1f - dimAlpha);
-            updateBackgroundColor(mBackground.getColor(), dimAlpha);
-        }
-    }
-
-    /**
-     * Updates the background and highlight colors for this header.
-     */
-    private void updateBackgroundColor(int color, float dimAlpha) {
-        if (mTask != null) {
-            mBackground.setColorAndDim(color, dimAlpha);
-            // TODO: Consider using the saturation of the color to adjust the lightness as well
-            ColorUtils.colorToHSL(color, mTmpHSL);
-            mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
-            mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
-            mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
-            invalidate();
-        }
-    }
-
-    /**
-     * Sets whether the background color should be darkened to differentiate from the primary color.
-     */
-    public void setShouldDarkenBackgroundColor(boolean flag) {
-        mShouldDarkenBackgroundColor = flag;
-    }
-
-    /**
-     * Binds the bar view to the task.
-     */
-    public void bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
-        mTask = t;
-
-        int primaryColor = disabledInSafeMode
-                ? mDisabledTaskBarBackgroundColor
-                : t.colorPrimary;
-        if (mBackground.getColor() != primaryColor) {
-            updateBackgroundColor(primaryColor, mDimAlpha);
-        }
-        if (!mTitleView.getText().toString().equals(t.title)) {
-            mTitleView.setText(t.title);
-        }
-        mTitleView.setContentDescription(t.titleDescription);
-        mTitleView.setTextColor(t.useLightOnPrimaryColor ?
-                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
-        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
-                mLightDismissDrawable : mDarkDismissDrawable);
-        mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription));
-        mDismissButton.setOnClickListener(this);
-        mDismissButton.setClickable(false);
-        ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
-
-        // In accessibility, a single click on the focused app info button will show it
-        if (touchExplorationEnabled) {
-            mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription));
-            mIconView.setOnClickListener(this);
-            mIconView.setClickable(true);
-        }
-    }
-
-    /**
-     * Called when the bound task's data has loaded and this view should update to reflect the
-     * changes.
-     */
-    public void onTaskDataLoaded() {
-        if (mTask != null && mTask.icon != null) {
-            mIconView.setImageDrawable(mTask.icon);
-        }
-    }
-
-    /** Unbinds the bar view from the task */
-    void unbindFromTask(boolean touchExplorationEnabled) {
-        mTask = null;
-        mIconView.setImageDrawable(null);
-        if (touchExplorationEnabled) {
-            mIconView.setClickable(false);
-        }
-    }
-
-    /** Animates this task bar if the user does not interact with the stack after a certain time. */
-    void startNoUserInteractionAnimation() {
-        int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
-        mDismissButton.setVisibility(View.VISIBLE);
-        mDismissButton.setClickable(true);
-        if (mDismissButton.getVisibility() == VISIBLE) {
-            mDismissButton.animate()
-                    .alpha(1f)
-                    .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                    .setDuration(duration)
-                    .start();
-        } else {
-            mDismissButton.setAlpha(1f);
-        }
-        if (mMoveTaskButton != null) {
-            if (mMoveTaskButton.getVisibility() == VISIBLE) {
-                mMoveTaskButton.setVisibility(View.VISIBLE);
-                mMoveTaskButton.setClickable(true);
-                mMoveTaskButton.animate()
-                        .alpha(1f)
-                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
-                        .setDuration(duration)
-                        .start();
-            } else {
-                mMoveTaskButton.setAlpha(1f);
-            }
-        }
-    }
-
-    /**
-     * Mark this task view that the user does has not interacted with the stack after a certain
-     * time.
-     */
-    public void setNoUserInteractionState() {
-        mDismissButton.setVisibility(View.VISIBLE);
-        mDismissButton.animate().cancel();
-        mDismissButton.setAlpha(1f);
-        mDismissButton.setClickable(true);
-        if (mMoveTaskButton != null) {
-            mMoveTaskButton.setVisibility(View.VISIBLE);
-            mMoveTaskButton.animate().cancel();
-            mMoveTaskButton.setAlpha(1f);
-            mMoveTaskButton.setClickable(true);
-        }
-    }
-
-    /**
-     * Resets the state tracking that the user has not interacted with the stack after a certain
-     * time.
-     */
-    void resetNoUserInteractionState() {
-        mDismissButton.setVisibility(View.INVISIBLE);
-        mDismissButton.setAlpha(0f);
-        mDismissButton.setClickable(false);
-        if (mMoveTaskButton != null) {
-            mMoveTaskButton.setVisibility(View.INVISIBLE);
-            mMoveTaskButton.setAlpha(0f);
-            mMoveTaskButton.setClickable(false);
-        }
-    }
-
-    @Override
-    protected int[] onCreateDrawableState(int extraSpace) {
-
-        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
-        // This is to prevent layer trashing when the view is pressed.
-        return new int[] {};
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v == mIconView) {
-            // In accessibility, a single click on the focused app info button will show it
-            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
-        } else if (v == mDismissButton) {
-            TaskView tv = Utilities.findParent(this, TaskView.class);
-            tv.dismissTask();
-
-            // Keep track of deletions by the dismiss button
-            MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
-                    Constants.Metrics.DismissSourceHeaderButton);
-        } else if (v == mMoveTaskButton) {
-            TaskView tv = Utilities.findParent(this, TaskView.class);
-            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false,
-                    mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED));
-        } else if (v == mAppInfoView) {
-            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
-        } else if (v == mAppIconView) {
-            hideAppOverlay(false /* immediate */);
-        }
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (v == mIconView) {
-            showAppOverlay();
-            return true;
-        } else if (v == mAppIconView) {
-            hideAppOverlay(false /* immediate */);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Shows the application overlay.
-     */
-    private void showAppOverlay() {
-        // Skip early if the task is invalid
-        SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices();
-        ComponentName cn = mTask.key.getComponent();
-        int userId = mTask.key.userId;
-        ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId);
-        if (activityInfo == null) {
-            return;
-        }
-
-        // Inflate the overlay if necessary
-        if (mAppOverlayView == null) {
-            mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this,
-                    R.id.app_overlay_stub).inflate();
-            mAppOverlayView.setBackground(mOverlayBackground);
-            mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
-            mAppIconView.setOnClickListener(this);
-            mAppIconView.setOnLongClickListener(this);
-            mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
-            mAppInfoView.setOnClickListener(this);
-            mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
-            updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
-        }
-
-        // Update the overlay contents for the current app
-        mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel(
-                activityInfo.applicationInfo, userId));
-        mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
-                mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
-        mAppIconView.setImageDrawable(getIconDrawableFactory().getBadgedIcon(
-                activityInfo.applicationInfo, userId));
-        mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
-                ? mLightInfoIcon
-                : mDarkInfoIcon);
-        mAppOverlayView.setVisibility(View.VISIBLE);
-
-        int x = mIconView.getLeft() + mIconView.getWidth() / 2;
-        int y = mIconView.getTop() + mIconView.getHeight() / 2;
-        Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
-                getWidth());
-        revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
-        revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        revealAnim.start();
-    }
-
-    /**
-     * Hide the application overlay.
-     */
-    private void hideAppOverlay(boolean immediate) {
-        // Skip if we haven't even loaded the overlay yet
-        if (mAppOverlayView == null) {
-            return;
-        }
-
-        if (immediate) {
-            mAppOverlayView.setVisibility(View.GONE);
-        } else {
-            int x = mIconView.getLeft() + mIconView.getWidth() / 2;
-            int y = mIconView.getTop() + mIconView.getHeight() / 2;
-            Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
-                    getWidth(), 0);
-            revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
-            revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-            revealAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mAppOverlayView.setVisibility(View.GONE);
-                }
-            });
-            revealAnim.start();
-        }
-    }
-
-    private static IconDrawableFactory getIconDrawableFactory() {
-        if (sDrawableFactory == null) {
-            sDrawableFactory = IconDrawableFactory.newInstance(AppGlobals.getInitialApplication());
-        }
-        return sDrawableFactory;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewThumbnail.java
deleted file mode 100644
index 68f85a5..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.LightingColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewDebug;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import java.io.PrintWriter;
-
-
-/**
- * The task thumbnail view.  It implements an image view that allows for animating the dim and
- * alpha of the thumbnail image.
- */
-public class TaskViewThumbnail extends View {
-
-    private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix();
-    private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix();
-
-    private Task mTask;
-
-    private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
-    private Rect mDisplayRect = new Rect();
-
-    // Drawing
-    @ViewDebug.ExportedProperty(category="recents")
-    protected Rect mTaskViewRect = new Rect();
-    @ViewDebug.ExportedProperty(category="recents")
-    protected Rect mThumbnailRect = new Rect();
-    @ViewDebug.ExportedProperty(category="recents")
-    protected float mThumbnailScale;
-    private float mFullscreenThumbnailScale = 1f;
-    /** The height, in pixels, of the task view's title bar. */
-    private int mTitleBarHeight;
-    private boolean mSizeToFit = false;
-    private boolean mOverlayHeaderOnThumbnailActionBar = true;
-    private ThumbnailData mThumbnailData;
-
-    protected int mCornerRadius;
-    @ViewDebug.ExportedProperty(category="recents")
-    private float mDimAlpha;
-    private Matrix mMatrix = new Matrix();
-    private Paint mDrawPaint = new Paint();
-    protected Paint mLockedPaint = new Paint();
-    protected Paint mBgFillPaint = new Paint();
-    protected BitmapShader mBitmapShader;
-    protected boolean mUserLocked = false;
-    private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
-
-    // Clip the top of the thumbnail against the opaque header bar that overlaps this view
-    private View mTaskBar;
-
-    // Visibility optimization, if the thumbnail height is less than the height of the header
-    // bar for the task view, then just mark this thumbnail view as invisible
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mInvisible;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    private boolean mDisabledInSafeMode;
-
-    public TaskViewThumbnail(Context context) {
-        this(context, null);
-    }
-
-    public TaskViewThumbnail(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mDrawPaint.setColorFilter(mLightingColorFilter);
-        mDrawPaint.setFilterBitmap(true);
-        mDrawPaint.setAntiAlias(true);
-        Resources res = getResources();
-        mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
-        mBgFillPaint.setColor(Color.WHITE);
-        mLockedPaint.setColor(Color.WHITE);
-        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
-    }
-
-    /**
-     * Called when the task view frame changes, allowing us to move the contents of the header
-     * to match the frame changes.
-     */
-    public void onTaskViewSizeChanged(int width, int height) {
-        // Return early if the bounds have not changed
-        if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
-            return;
-        }
-
-        mTaskViewRect.set(0, 0, width, height);
-        setLeftTopRightBottom(0, 0, width, height);
-        updateThumbnailMatrix();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mInvisible) {
-            return;
-        }
-
-        int viewWidth = mTaskViewRect.width();
-        int viewHeight = mTaskViewRect.height();
-        int thumbnailWidth = Math.min(viewWidth,
-                (int) (mThumbnailRect.width() * mThumbnailScale));
-        int thumbnailHeight = Math.min(viewHeight,
-                (int) (mThumbnailRect.height() * mThumbnailScale));
-
-        if (mUserLocked) {
-            canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
-                    mLockedPaint);
-        } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
-            int topOffset = 0;
-            if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) {
-                topOffset = mTaskBar.getHeight() - mCornerRadius;
-            }
-
-            // Draw the background, there will be some small overdraw with the thumbnail
-            if (thumbnailWidth < viewWidth) {
-                // Portrait thumbnail on a landscape task view
-                canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), topOffset,
-                        viewWidth, viewHeight,
-                        mCornerRadius, mCornerRadius, mBgFillPaint);
-            }
-            if (thumbnailHeight < viewHeight) {
-                // Landscape thumbnail on a portrait task view
-                canvas.drawRoundRect(0, Math.max(topOffset, thumbnailHeight - mCornerRadius),
-                        viewWidth, viewHeight,
-                        mCornerRadius, mCornerRadius, mBgFillPaint);
-            }
-
-            // Draw the thumbnail
-            canvas.drawRoundRect(0, topOffset, thumbnailWidth, thumbnailHeight,
-                    mCornerRadius, mCornerRadius, mDrawPaint);
-        } else {
-            canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
-                    mBgFillPaint);
-        }
-    }
-
-    /** Sets the thumbnail to a given bitmap. */
-    void setThumbnail(ThumbnailData thumbnailData) {
-        if (thumbnailData != null && thumbnailData.thumbnail != null) {
-            Bitmap bm = thumbnailData.thumbnail;
-            bm.prepareToDraw();
-            mFullscreenThumbnailScale = thumbnailData.scale;
-            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-            mDrawPaint.setShader(mBitmapShader);
-            mThumbnailRect.set(0, 0,
-                    bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
-                    bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
-            mThumbnailData = thumbnailData;
-            updateThumbnailMatrix();
-            updateThumbnailPaintFilter();
-        } else {
-            mBitmapShader = null;
-            mDrawPaint.setShader(null);
-            mThumbnailRect.setEmpty();
-            mThumbnailData = null;
-        }
-    }
-
-    /** Updates the paint to draw the thumbnail. */
-    void updateThumbnailPaintFilter() {
-        if (mInvisible) {
-            return;
-        }
-        int mul = (int) ((1.0f - mDimAlpha) * 255);
-        if (mBitmapShader != null) {
-            if (mDisabledInSafeMode) {
-                // Brightness: C-new = C-old*(1-amount) + amount
-                TMP_FILTER_COLOR_MATRIX.setSaturation(0);
-                float scale = 1f - mDimAlpha;
-                float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray();
-                mat[0] = scale;
-                mat[6] = scale;
-                mat[12] = scale;
-                mat[4] = mDimAlpha * 255f;
-                mat[9] = mDimAlpha * 255f;
-                mat[14] = mDimAlpha * 255f;
-                TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX);
-                ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX);
-                mDrawPaint.setColorFilter(filter);
-                mBgFillPaint.setColorFilter(filter);
-                mLockedPaint.setColorFilter(filter);
-            } else {
-                mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
-                mDrawPaint.setColorFilter(mLightingColorFilter);
-                mDrawPaint.setColor(0xFFffffff);
-                mBgFillPaint.setColorFilter(mLightingColorFilter);
-                mLockedPaint.setColorFilter(mLightingColorFilter);
-            }
-        } else {
-            int grey = mul;
-            mDrawPaint.setColorFilter(null);
-            mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
-        }
-        if (!mInvisible) {
-            invalidate();
-        }
-    }
-
-    /**
-     * Updates the scale of the bitmap relative to this view.
-     */
-    public void updateThumbnailMatrix() {
-        mThumbnailScale = 1f;
-        if (mBitmapShader != null && mThumbnailData != null) {
-            if (mTaskViewRect.isEmpty()) {
-                // If we haven't measured , skip the thumbnail drawing and only draw the background
-                // color
-                mThumbnailScale = 0f;
-            } else if (mSizeToFit) {
-                // Make sure we fill the entire space regardless of the orientation.
-                float viewAspectRatio = (float) mTaskViewRect.width() /
-                        (float) (mTaskViewRect.height() - mTitleBarHeight);
-                float thumbnailAspectRatio =
-                        (float) mThumbnailRect.width() / (float) mThumbnailRect.height();
-                if (viewAspectRatio > thumbnailAspectRatio) {
-                    mThumbnailScale =
-                            (float) mTaskViewRect.width() / (float) mThumbnailRect.width();
-                } else {
-                    mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
-                            / (float) mThumbnailRect.height();
-                }
-            } else {
-                float invThumbnailScale = 1f / mFullscreenThumbnailScale;
-                if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
-                    if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
-                        // If we are in the same orientation as the screenshot, just scale it to the
-                        // width of the task view
-                        mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
-                    } else {
-                        // Scale the landscape thumbnail up to app size, then scale that to the task
-                        // view size to match other portrait screenshots
-                        mThumbnailScale = invThumbnailScale *
-                                ((float) mTaskViewRect.width() / mDisplayRect.width());
-                    }
-                } else {
-                    // Otherwise, scale the screenshot to fit 1:1 in the current orientation
-                    mThumbnailScale = invThumbnailScale;
-                }
-            }
-            mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
-                    -mThumbnailData.insets.top * mFullscreenThumbnailScale);
-            mMatrix.postScale(mThumbnailScale, mThumbnailScale);
-            mBitmapShader.setLocalMatrix(mMatrix);
-        }
-        if (!mInvisible) {
-            invalidate();
-        }
-    }
-
-    /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
-    public void setSizeToFit(boolean flag) {
-        mSizeToFit = flag;
-    }
-
-    /**
-     * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
-     * be stacked just above it.
-     */
-    public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
-        mOverlayHeaderOnThumbnailActionBar = flag;
-    }
-
-    /** Updates the clip rect based on the given task bar. */
-    void updateClipToTaskBar(View taskBar) {
-        mTaskBar = taskBar;
-        invalidate();
-    }
-
-    /** Updates the visibility of the the thumbnail. */
-    void updateThumbnailVisibility(int clipBottom) {
-        boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
-        if (invisible != mInvisible) {
-            mInvisible = invisible;
-            if (!mInvisible) {
-                updateThumbnailPaintFilter();
-            }
-        }
-    }
-
-    /**
-     * Sets the dim alpha, only used when we are not using hardware layers.
-     * (see RecentsConfiguration.useHardwareLayers)
-     */
-    public void setDimAlpha(float dimAlpha) {
-        mDimAlpha = dimAlpha;
-        updateThumbnailPaintFilter();
-    }
-
-    /**
-     * Returns the {@link Paint} used to draw a task screenshot, or {@link #mLockedPaint} if the
-     * thumbnail shouldn't be drawn because it belongs to a locked user.
-     */
-    protected Paint getDrawPaint() {
-        if (mUserLocked) {
-            return mLockedPaint;
-        }
-        return mDrawPaint;
-    }
-
-    /**
-     * Binds the thumbnail view to the task.
-     */
-    void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) {
-        mTask = t;
-        mDisabledInSafeMode = disabledInSafeMode;
-        mDisplayOrientation = displayOrientation;
-        mDisplayRect.set(displayRect);
-        if (t.colorBackground != 0) {
-            mBgFillPaint.setColor(t.colorBackground);
-        }
-        if (t.colorPrimary != 0) {
-            mLockedPaint.setColor(t.colorPrimary);
-        }
-        mUserLocked = t.isLocked;
-        EventBus.getDefault().register(this);
-    }
-
-    /**
-     * Called when the bound task's data has loaded and this view should update to reflect the
-     * changes.
-     */
-    void onTaskDataLoaded(ThumbnailData thumbnailData) {
-        setThumbnail(thumbnailData);
-    }
-
-    /** Unbinds the thumbnail view from the task */
-    void unbindFromTask() {
-        mTask = null;
-        setThumbnail(null);
-        EventBus.getDefault().unregister(this);
-    }
-
-    public final void onBusEvent(TaskSnapshotChangedEvent event) {
-        if (mTask == null || event.taskId != mTask.key.id || event.thumbnailData == null
-                || event.thumbnailData.thumbnail == null) {
-            return;
-        }
-        setThumbnail(event.thumbnailData);
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        writer.print(prefix); writer.print("TaskViewThumbnail");
-        writer.print(" mTaskViewRect="); writer.print(Utilities.dumpRect(mTaskViewRect));
-        writer.print(" mThumbnailRect="); writer.print(Utilities.dumpRect(mThumbnailRect));
-        writer.print(" mThumbnailScale="); writer.print(mThumbnailScale);
-        writer.print(" mDimAlpha="); writer.print(mDimAlpha);
-        writer.println();
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewTransform.java
deleted file mode 100644
index 48a7336..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.Property;
-import android.view.View;
-
-import com.android.systemui.recents.utilities.AnimationProps;
-import com.android.systemui.recents.utilities.Utilities;
-
-import java.util.ArrayList;
-
-/**
- * The visual properties for a {@link TaskView}.
- */
-public class TaskViewTransform {
-
-    public static final Property<View, Rect> LTRB =
-            new Property<View, Rect>(Rect.class, "leftTopRightBottom") {
-
-                private Rect mTmpRect = new Rect();
-
-                @Override
-                public void set(View v, Rect ltrb) {
-                    v.setLeftTopRightBottom(ltrb.left, ltrb.top, ltrb.right, ltrb.bottom);
-                }
-
-                @Override
-                public Rect get(View v) {
-                    mTmpRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-                    return mTmpRect;
-                }
-            };
-
-    public float translationZ = 0;
-    public float scale = 1f;
-    public float alpha = 1f;
-    public float dimAlpha = 0f;
-    public float viewOutlineAlpha = 0f;
-
-    public boolean visible = false;
-
-    // This is a window-space rect used for positioning the task in the stack
-    public RectF rect = new RectF();
-
-    /**
-     * Fills int this transform from the state of the given TaskView.
-     */
-    public void fillIn(TaskView tv) {
-        translationZ = tv.getTranslationZ();
-        scale = tv.getScaleX();
-        alpha = tv.getAlpha();
-        visible = true;
-        dimAlpha = tv.getDimAlpha();
-        viewOutlineAlpha = tv.getViewBounds().getAlpha();
-        rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
-    }
-
-    /**
-     * Copies the transform state from another {@link TaskViewTransform}.
-     */
-    public void copyFrom(TaskViewTransform other) {
-        translationZ = other.translationZ;
-        scale = other.scale;
-        alpha = other.alpha;
-        visible = other.visible;
-        dimAlpha = other.dimAlpha;
-        viewOutlineAlpha = other.viewOutlineAlpha;
-        rect.set(other.rect);
-    }
-
-    /**
-     * @return whether {@param other} is the same transform as this
-     */
-    public boolean isSame(TaskViewTransform other) {
-        return translationZ == other.translationZ
-                && scale == other.scale
-                && other.alpha == alpha
-                && dimAlpha == other.dimAlpha
-                && visible == other.visible
-                && rect.equals(other.rect);
-    }
-
-    /**
-     * Resets the current transform.
-     */
-    public void reset() {
-        translationZ = 0;
-        scale = 1f;
-        alpha = 1f;
-        dimAlpha = 0f;
-        viewOutlineAlpha = 0f;
-        visible = false;
-        rect.setEmpty();
-    }
-
-    /** Convenience functions to compare against current property values */
-    public boolean hasAlphaChangedFrom(float v) {
-        return (Float.compare(alpha, v) != 0);
-    }
-
-    public boolean hasScaleChangedFrom(float v) {
-        return (Float.compare(scale, v) != 0);
-    }
-
-    public boolean hasTranslationZChangedFrom(float v) {
-        return (Float.compare(translationZ, v) != 0);
-    }
-
-    public boolean hasRectChangedFrom(View v) {
-        return ((int) rect.left != v.getLeft()) || ((int) rect.right != v.getRight()) ||
-                ((int) rect.top != v.getTop()) || ((int) rect.bottom != v.getBottom());
-    }
-
-    /**
-     * Applies this transform to a view.
-     */
-    public void applyToTaskView(TaskView v, ArrayList<Animator> animators,
-            AnimationProps animation, boolean allowShadows) {
-        // Return early if not visible
-        if (!visible) {
-            return;
-        }
-
-        if (animation.isImmediate()) {
-            if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
-                v.setTranslationZ(translationZ);
-            }
-            if (hasScaleChangedFrom(v.getScaleX())) {
-                v.setScaleX(scale);
-                v.setScaleY(scale);
-            }
-            if (hasAlphaChangedFrom(v.getAlpha())) {
-                v.setAlpha(alpha);
-            }
-            if (hasRectChangedFrom(v)) {
-                v.setLeftTopRightBottom((int) rect.left, (int) rect.top, (int) rect.right,
-                        (int) rect.bottom);
-            }
-        } else {
-            if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
-                ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.TRANSLATION_Z,
-                        v.getTranslationZ(), translationZ);
-                animators.add(animation.apply(AnimationProps.TRANSLATION_Z, anim));
-            }
-            if (hasScaleChangedFrom(v.getScaleX())) {
-                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
-                        PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale),
-                        PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale));
-                animators.add(animation.apply(AnimationProps.SCALE, anim));
-            }
-            if (hasAlphaChangedFrom(v.getAlpha())) {
-                ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha);
-                animators.add(animation.apply(AnimationProps.ALPHA, anim));
-            }
-            if (hasRectChangedFrom(v)) {
-                Rect fromViewRect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-                Rect toViewRect = new Rect();
-                rect.round(toViewRect);
-                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
-                        PropertyValuesHolder.ofObject(LTRB, Utilities.RECT_EVALUATOR,
-                                fromViewRect, toViewRect));
-                animators.add(animation.apply(AnimationProps.BOUNDS, anim));
-            }
-        }
-    }
-
-    /** Reset the transform on a view. */
-    public static void reset(TaskView v) {
-        v.setTranslationX(0f);
-        v.setTranslationY(0f);
-        v.setTranslationZ(0f);
-        v.setScaleX(1f);
-        v.setScaleY(1f);
-        v.setAlpha(1f);
-        v.getViewBounds().setClipBottom(0);
-        v.setLeftTopRightBottom(0, 0, 0, 0);
-    }
-
-    @Override
-    public String toString() {
-        return "R: " + rect + " V: " + visible;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/ViewPool.java
deleted file mode 100644
index a287fe6..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/ViewPool.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-
-/* A view pool to manage more views than we can visibly handle */
-public class ViewPool<V, T> {
-
-    /* An interface to the consumer of a view pool */
-    public interface ViewPoolConsumer<V, T> {
-        public V createView(Context context);
-        public void onReturnViewToPool(V v);
-        public void onPickUpViewFromPool(V v, T prepareData, boolean isNewView);
-        public boolean hasPreferredData(V v, T preferredData);
-    }
-
-    Context mContext;
-    ViewPoolConsumer<V, T> mViewCreator;
-    LinkedList<V> mPool = new LinkedList<V>();
-
-    /** Initializes the pool with a fixed predetermined pool size */
-    public ViewPool(Context context, ViewPoolConsumer<V, T> viewCreator) {
-        mContext = context;
-        mViewCreator = viewCreator;
-    }
-
-    /** Returns a view into the pool */
-    void returnViewToPool(V v) {
-        mViewCreator.onReturnViewToPool(v);
-        mPool.push(v);
-    }
-
-    /** Gets a view from the pool and prepares it */
-    V pickUpViewFromPool(T preferredData, T prepareData) {
-        V v = null;
-        boolean isNewView = false;
-        if (mPool.isEmpty()) {
-            v = mViewCreator.createView(mContext);
-            isNewView = true;
-        } else {
-            // Try and find a preferred view
-            Iterator<V> iter = mPool.iterator();
-            while (iter.hasNext()) {
-                V vpv = iter.next();
-                if (mViewCreator.hasPreferredData(vpv, preferredData)) {
-                    v = vpv;
-                    iter.remove();
-                    break;
-                }
-            }
-            // Otherwise, just grab the first view
-            if (v == null) {
-                v = mPool.pop();
-            }
-        }
-        mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView);
-        return v;
-    }
-
-    /**
-     * Returns the list of views in the pool.
-     */
-    List<V> getViews() {
-        return mPool;
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
deleted file mode 100644
index a029478..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
+++ /dev/null
@@ -1,32 +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.recents.views.grid;
-
-import android.view.View;
-import com.android.systemui.recents.views.AnimateableViewBounds;
-
-/* An outline provider for grid-based task views. */
-class AnimateableGridViewBounds extends AnimateableViewBounds {
-
-    public AnimateableGridViewBounds(View source, int cornerRadius) {
-        super(source, cornerRadius);
-    }
-
-    @Override
-    protected void updateClipBounds() {
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskView.java
deleted file mode 100644
index 8b4700c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskView.java
+++ /dev/null
@@ -1,72 +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.recents.views.grid;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import com.android.systemui.R;
-import com.android.systemui.recents.views.AnimateableViewBounds;
-import com.android.systemui.recents.views.TaskView;
-
-public class GridTaskView extends TaskView {
-
-    /** The height, in pixels, of the header view. */
-    private int mHeaderHeight;
-
-    public GridTaskView(Context context) {
-        this(context, null);
-    }
-
-    public GridTaskView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mHeaderHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.recents_grid_task_view_header_height);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        // Show the full thumbnail and don't overlap with the header.
-        mThumbnailView.setSizeToFit(true);
-        mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false);
-        mThumbnailView.updateThumbnailMatrix();
-        mThumbnailView.setTranslationY(mHeaderHeight);
-        mHeaderView.setShouldDarkenBackgroundColor(true);
-    }
-
-    @Override
-    protected AnimateableViewBounds createOutlineProvider() {
-        return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize(
-            R.dimen.recents_task_view_shadow_rounded_corners_radius));
-    }
-
-    @Override
-    protected void onConfigurationChanged() {
-        super.onConfigurationChanged();
-        mHeaderHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.recents_grid_task_view_header_height);
-        mThumbnailView.setTranslationY(mHeaderHeight);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
deleted file mode 100644
index 2d7cfb1..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ /dev/null
@@ -1,192 +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.recents.views.grid;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.util.AttributeSet;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.views.TaskViewThumbnail;
-
-public class GridTaskViewThumbnail extends TaskViewThumbnail {
-
-    private final Path mThumbnailOutline = new Path();
-    private final Path mRestBackgroundOutline = new Path();
-    // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
-    // be updated.
-    private boolean mUpdateThumbnailOutline = true;
-
-    public GridTaskViewThumbnail(Context context) {
-        this(context, null);
-    }
-
-    public GridTaskViewThumbnail(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public GridTaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public GridTaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr,
-        int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mCornerRadius = getResources().getDimensionPixelSize(
-                R.dimen.recents_grid_task_view_rounded_corners_radius);
-    }
-
-    /**
-     * Called when the task view frame changes, allowing us to move the contents of the header
-     * to match the frame changes.
-     */
-    public void onTaskViewSizeChanged(int width, int height) {
-        mUpdateThumbnailOutline = true;
-        super.onTaskViewSizeChanged(width, height);
-    }
-
-    /**
-     * Updates the scale of the bitmap relative to this view.
-     */
-    public void updateThumbnailMatrix() {
-        mUpdateThumbnailOutline = true;
-        super.updateThumbnailMatrix();
-    }
-
-    private void updateThumbnailOutline() {
-        final int titleHeight = getResources().getDimensionPixelSize(
-            R.dimen.recents_grid_task_view_header_height);
-        final int viewWidth = mTaskViewRect.width();
-        final int viewHeight = mTaskViewRect.height() - titleHeight;
-        final int thumbnailWidth = Math.min(viewWidth,
-            (int) (mThumbnailRect.width() * mThumbnailScale));
-        final int thumbnailHeight = Math.min(viewHeight,
-            (int) (mThumbnailRect.height() * mThumbnailScale));
-
-        if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
-            // Draw the thumbnail, we only round the bottom corners:
-            //
-            // outerLeft                outerRight
-            //    <----------------------->            mRestBackgroundOutline
-            //    _________________________            (thumbnailWidth < viewWidth)
-            //    |_______________________| outerTop     A ____ B
-            //    |                       |    ↑           |  |
-            //    |                       |    |           |  |
-            //    |                       |    |           |  |
-            //    |                       |    |           |  | C
-            //    \_______________________/    ↓           |__/
-            //  mCornerRadius             outerBottom    E    D
-            //
-            //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
-            //  A _________________________ B
-            //    |                       | C
-            //  F \_______________________/
-            //    E                       D
-            final int outerLeft = 0;
-            final int outerTop = 0;
-            final int outerRight = outerLeft + thumbnailWidth;
-            final int outerBottom = outerTop + thumbnailHeight;
-            createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
-
-            if (thumbnailWidth < viewWidth) {
-                final int l = Math.max(0, outerRight - mCornerRadius);
-                final int r = outerRight;
-                final int t = outerTop;
-                final int b = outerBottom;
-                mRestBackgroundOutline.reset();
-                mRestBackgroundOutline.moveTo(l, t); // A
-                mRestBackgroundOutline.lineTo(r, t); // B
-                mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
-                mRestBackgroundOutline.arcTo(r -  2 * mCornerRadius, b - 2 * mCornerRadius, r, b,
-                        0, 90, false); // D
-                mRestBackgroundOutline.lineTo(l, b); // E
-                mRestBackgroundOutline.lineTo(l, t); // A
-                mRestBackgroundOutline.close();
-
-            }
-            if (thumbnailHeight < viewHeight) {
-                final int l = outerLeft;
-                final int r = outerRight;
-                final int t = Math.max(0, thumbnailHeight - mCornerRadius);
-                final int b = outerBottom;
-                mRestBackgroundOutline.reset();
-                mRestBackgroundOutline.moveTo(l, t); // A
-                mRestBackgroundOutline.lineTo(r, t); // B
-                mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
-                mRestBackgroundOutline.arcTo(r -  2 * mCornerRadius, b - 2 * mCornerRadius, r, b,
-                        0, 90, false); // D
-                mRestBackgroundOutline.lineTo(l + mCornerRadius, b); // E
-                mRestBackgroundOutline.arcTo(l, b - 2 * mCornerRadius, l + 2 * mCornerRadius, b,
-                        90, 90, false); // F
-                mRestBackgroundOutline.lineTo(l, t); // A
-                mRestBackgroundOutline.close();
-            }
-        } else {
-            createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
-        }
-    }
-
-    private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
-            Path outPath) {
-        outPath.reset();
-        outPath.moveTo(outerLeft, outerTop);
-        outPath.lineTo(outerRight, outerTop);
-        outPath.lineTo(outerRight, outerBottom - mCornerRadius);
-        outPath.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
-                outerBottom, 0, 90, false);
-        outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
-        outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
-                outerBottom, 90, 90, false);
-        outPath.lineTo(outerLeft, outerTop);
-        outPath.close();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        final int titleHeight = getResources().getDimensionPixelSize(
-            R.dimen.recents_grid_task_view_header_height);
-        final int viewWidth = mTaskViewRect.width();
-        final int viewHeight = mTaskViewRect.height() - titleHeight;
-        final int thumbnailWidth = Math.min(viewWidth,
-            (int) (mThumbnailRect.width() * mThumbnailScale));
-        final int thumbnailHeight = Math.min(viewHeight,
-            (int) (mThumbnailRect.height() * mThumbnailScale));
-
-        if (mUpdateThumbnailOutline) {
-            updateThumbnailOutline();
-            mUpdateThumbnailOutline = false;
-        }
-
-        if (mUserLocked) {
-            canvas.drawPath(mThumbnailOutline, mLockedPaint);
-        } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
-            // Draw the background, there will be some small overdraw with the thumbnail
-            if (thumbnailWidth < viewWidth) {
-                // Portrait thumbnail on a landscape task view
-                canvas.drawPath(mRestBackgroundOutline, mBgFillPaint);
-            }
-            if (thumbnailHeight < viewHeight) {
-                // Landscape thumbnail on a portrait task view
-                canvas.drawPath(mRestBackgroundOutline, mBgFillPaint);
-            }
-            canvas.drawPath(mThumbnailOutline, getDrawPaint());
-        } else {
-            canvas.drawPath(mThumbnailOutline, mBgFillPaint);
-        }
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
deleted file mode 100644
index 719eaa7..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ /dev/null
@@ -1,325 +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.recents.views.grid;
-
-import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.*;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.WindowManager;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewTransform;
-
-import java.util.ArrayList;
-
-public class TaskGridLayoutAlgorithm  {
-
-    private final String TAG = "TaskGridLayoutAlgorithm";
-    public static final int MAX_LAYOUT_TASK_COUNT = 8;
-
-    /** The horizontal padding around the whole recents view. */
-    private int mPaddingLeftRight;
-    /** The vertical padding around the whole recents view. */
-    private int mPaddingTopBottom;
-    /** The padding between task views. */
-    private int mPaddingTaskView;
-
-    private Rect mWindowRect;
-    private Point mScreenSize = new Point();
-
-    private Rect mTaskGridRect;
-
-    /** The height, in pixels, of each task view's title bar. */
-    private int mTitleBarHeight;
-
-    /** The aspect ratio of each task thumbnail, without the title bar. */
-    private float mAppAspectRatio;
-    private Rect mSystemInsets = new Rect();
-
-    /** The thickness of the focused task view frame. */
-    private int mFocusedFrameThickness;
-
-    /**
-     * When the amount of tasks is determined, the size and position of every task view can be
-     * decided. Each instance of TaskGridRectInfo store the task view information for a certain
-     * amount of tasks.
-     */
-    class TaskGridRectInfo {
-        Rect size;
-        int[] xOffsets;
-        int[] yOffsets;
-        int tasksPerLine;
-        int lines;
-
-        TaskGridRectInfo(int taskCount) {
-            size = new Rect();
-            xOffsets = new int[taskCount];
-            yOffsets = new int[taskCount];
-
-            int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
-            tasksPerLine = getTasksPerLine(layoutTaskCount);
-            lines = layoutTaskCount < 4 ? 1 : 2;
-
-            // A couple of special cases.
-            boolean landscapeWindow = mWindowRect.width() > mWindowRect.height();
-            boolean landscapeTaskView = mAppAspectRatio > 1;
-            // If we're in portrait but task views are landscape, show more lines of fewer tasks.
-            if (!landscapeWindow && landscapeTaskView) {
-                tasksPerLine = layoutTaskCount < 2 ? 1 : 2;
-                lines = layoutTaskCount < 3 ? 1 : (
-                        layoutTaskCount < 5 ? 2 : (
-                                layoutTaskCount < 7 ? 3 : 4));
-            }
-            // If we're in landscape but task views are portrait, show fewer lines of more tasks.
-            if (landscapeWindow && !landscapeTaskView) {
-                tasksPerLine = layoutTaskCount < 7 ? layoutTaskCount : 6;
-                lines = layoutTaskCount < 7 ? 1 : 2;
-            }
-
-            int taskWidth, taskHeight;
-            int maxTaskWidth = (mWindowRect.width() - 2 * mPaddingLeftRight
-                - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
-            int maxTaskHeight = (mWindowRect.height() - 2 * mPaddingTopBottom
-                - (lines - 1) * mPaddingTaskView) / lines;
-
-            if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
-                // Width bound.
-                taskWidth = maxTaskWidth;
-                // Here we should round the height to the nearest integer.
-                taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight + 0.5);
-            } else {
-                // Height bound.
-                taskHeight = maxTaskHeight;
-                // Here we should round the width to the nearest integer.
-                taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio + 0.5);
-            }
-            size.set(0, 0, taskWidth, taskHeight);
-
-            int emptySpaceX = mWindowRect.width() - 2 * mPaddingLeftRight
-                - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
-            int emptySpaceY = mWindowRect.height() - 2 * mPaddingTopBottom
-                - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
-            for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) {
-                // We also need to invert the index in order to display the most recent tasks first.
-                int taskLayoutIndex = taskCount - taskIndex - 1;
-
-                int xIndex = taskLayoutIndex % tasksPerLine;
-                int yIndex = taskLayoutIndex / tasksPerLine;
-                xOffsets[taskIndex] = mWindowRect.left +
-                    emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
-                yOffsets[taskIndex] = mWindowRect.top +
-                    emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
-            }
-        }
-
-        private int getTasksPerLine(int taskCount) {
-            switch(taskCount) {
-                case 0:
-                    return 0;
-                case 1:
-                    return 1;
-                case 2:
-                case 4:
-                    return 2;
-                case 3:
-                case 5:
-                case 6:
-                    return 3;
-                case 7:
-                case 8:
-                    return 4;
-                default:
-                    throw new IllegalArgumentException("Unsupported task count " + taskCount);
-            }
-        }
-    }
-
-    /**
-     * We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there
-     * are k tasks.
-     */
-    private TaskGridRectInfo[] mTaskGridRectInfoList;
-
-    public TaskGridLayoutAlgorithm(Context context) {
-        reloadOnConfigurationChange(context);
-    }
-
-    public void reloadOnConfigurationChange(Context context) {
-        Resources res = context.getResources();
-        mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
-        mFocusedFrameThickness = res.getDimensionPixelSize(
-            R.dimen.recents_grid_task_view_focused_frame_thickness);
-
-        mTaskGridRect = new Rect();
-        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
-
-        WindowManager windowManager = (WindowManager) context
-                .getSystemService(Context.WINDOW_SERVICE);
-        windowManager.getDefaultDisplay().getRealSize(mScreenSize);
-
-        updateAppAspectRatio();
-    }
-
-    /**
-     * Returns the proper task view transform of a certain task view, according to its index and the
-     * amount of task views.
-     * @param taskIndex     The index of the task view whose transform we want. It's never greater
-     *                      than {@link MAX_LAYOUT_TASK_COUNT}.
-     * @param taskCount     The current amount of task views.
-     * @param transformOut  The result transform that this method returns.
-     * @param stackLayout   The base stack layout algorithm.
-     * @return  The expected transform of the (taskIndex)th task view.
-     */
-    public TaskViewTransform getTransform(int taskIndex, int taskCount,
-        TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
-        if (taskCount == 0) {
-            transformOut.reset();
-            return transformOut;
-        }
-
-        TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
-        mTaskGridRect.set(gridInfo.size);
-
-        int x = gridInfo.xOffsets[taskIndex];
-        int y = gridInfo.yOffsets[taskIndex];
-        float z = stackLayout.mMaxTranslationZ;
-
-        // We always set the dim alpha to 0, since we don't want grid task views to dim.
-        float dimAlpha = 0f;
-        // We always set the alpha of the view outline to 1, to make sure the shadow is visible.
-        float viewOutlineAlpha = 1f;
-
-        // We also need to invert the index in order to display the most recent tasks first.
-        int taskLayoutIndex = taskCount - taskIndex - 1;
-        boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT;
-
-        // Fill out the transform
-        transformOut.scale = 1f;
-        transformOut.alpha = isTaskViewVisible ? 1f : 0f;
-        transformOut.translationZ = z;
-        transformOut.dimAlpha = dimAlpha;
-        transformOut.viewOutlineAlpha = viewOutlineAlpha;
-        transformOut.rect.set(mTaskGridRect);
-        transformOut.rect.offset(x, y);
-        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-        // We only show the 8 most recent tasks.
-        transformOut.visible = isTaskViewVisible;
-        return transformOut;
-    }
-
-    /**
-     * Return the proper task index to focus for arrow key navigation.
-     * @param taskCount             The amount of tasks.
-     * @param currentFocusedIndex   The index of the currently focused task.
-     * @param direction             The direction we're navigating.
-     * @return  The index of the task that should get the focus.
-     */
-    public int navigateFocus(int taskCount, int currentFocusedIndex, Direction direction) {
-        if (taskCount < 1 || taskCount > MAX_LAYOUT_TASK_COUNT) {
-            return -1;
-        }
-        if (currentFocusedIndex == -1) {
-            return 0;
-        }
-        int newIndex = currentFocusedIndex;
-        final TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
-        final int currentLine = (taskCount - 1 - currentFocusedIndex) / gridInfo.tasksPerLine;
-        switch (direction) {
-            case UP:
-                newIndex += gridInfo.tasksPerLine;
-                newIndex = newIndex >= taskCount ? currentFocusedIndex : newIndex;
-                break;
-            case DOWN:
-                newIndex -= gridInfo.tasksPerLine;
-                newIndex = newIndex < 0 ? currentFocusedIndex : newIndex;
-                break;
-            case LEFT:
-                newIndex++;
-                final int leftMostIndex = (taskCount - 1) - currentLine * gridInfo.tasksPerLine;
-                newIndex = newIndex > leftMostIndex ? currentFocusedIndex : newIndex;
-                break;
-            case RIGHT:
-                newIndex--;
-                int rightMostIndex =
-                    (taskCount - 1) - (currentLine + 1) * gridInfo.tasksPerLine + 1;
-                rightMostIndex = rightMostIndex < 0 ? 0 : rightMostIndex;
-                newIndex = newIndex < rightMostIndex ? currentFocusedIndex : newIndex;
-                break;
-        }
-        return newIndex;
-    }
-
-    public void initialize(Rect windowRect) {
-        mWindowRect = windowRect;
-        // Define paddings in terms of percentage of the total area.
-        mPaddingLeftRight = (int) (0.025f * Math.min(mWindowRect.width(), mWindowRect.height()));
-        mPaddingTopBottom = (int) (0.1 * mWindowRect.height());
-
-        // Pre-calculate the positions and offsets of task views so that we can reuse them directly
-        // in the future.
-        mTaskGridRectInfoList = new TaskGridRectInfo[MAX_LAYOUT_TASK_COUNT];
-        for (int i = 0; i < MAX_LAYOUT_TASK_COUNT; i++) {
-            mTaskGridRectInfoList[i] = new TaskGridRectInfo(i + 1);
-        }
-    }
-
-    public void setSystemInsets(Rect systemInsets) {
-        mSystemInsets = systemInsets;
-        updateAppAspectRatio();
-    }
-
-    private void updateAppAspectRatio() {
-        int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
-        int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
-        mAppAspectRatio = (float) usableWidth / (float) usableHeight;
-    }
-
-    public Rect getStackActionButtonRect() {
-        Rect buttonRect = new Rect(mWindowRect);
-        buttonRect.right -= mPaddingLeftRight;
-        buttonRect.left += mPaddingLeftRight;
-        buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
-        return buttonRect;
-    }
-
-    public void updateTaskGridRect(int taskCount) {
-        if (taskCount > 0) {
-            TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
-            mTaskGridRect.set(gridInfo.size);
-        }
-    }
-
-    public Rect getTaskGridRect() {
-        return mTaskGridRect;
-    }
-
-    public int getFocusFrameThickness() {
-        return mFocusedFrameThickness;
-    }
-
-    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
-        int visibleCount = Math.min(TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT, tasks.size());
-        return new VisibilityReport(visibleCount, visibleCount);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
deleted file mode 100644
index 1655f6c..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ /dev/null
@@ -1,141 +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.recents.views.grid;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-
-import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
-import com.android.systemui.R;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
-
-public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
-
-    private TaskStackView mSv;
-    private TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
-    public TaskViewFocusFrame(Context context) {
-        this(context, null);
-    }
-
-    public TaskViewFocusFrame(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TaskViewFocusFrame(Context context, AttributeSet attrs, int defStyleAttr,
-        int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        setBackground(mContext.getDrawable(
-            R.drawable.recents_grid_task_view_focus_frame_background));
-        setFocusable(false);
-        hide();
-    }
-
-    public TaskViewFocusFrame(Context context, TaskStackView stackView,
-        TaskGridLayoutAlgorithm taskGridLayoutAlgorithm) {
-        this(context);
-        mSv = stackView;
-        mTaskGridLayoutAlgorithm = taskGridLayoutAlgorithm;
-    }
-
-    /**
-     * Measure the width and height of the focus frame according to the current grid task view size.
-     */
-    public void measure() {
-        int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
-        Rect rect = mTaskGridLayoutAlgorithm.getTaskGridRect();
-        measure(
-            MeasureSpec.makeMeasureSpec(rect.width() + thickness * 2, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(rect.height() + thickness * 2, MeasureSpec.EXACTLY));
-    }
-
-    /**
-     * Layout the focus frame with its size.
-     */
-    public void layout() {
-        layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
-    }
-
-    /**
-     * Update the current size of grid task view and the focus frame.
-     */
-    public void resize() {
-        if (mSv.useGridLayout()) {
-            mTaskGridLayoutAlgorithm.updateTaskGridRect(mSv.getStack().getTaskCount());
-            measure();
-            requestLayout();
-        }
-    }
-
-    /**
-     * Move the task view focus frame to surround the newly focused view. If it's {@code null} or
-     * it's not an instance of GridTaskView, we hide the focus frame.
-     * @param newFocus The newly focused view.
-     */
-    public void moveGridTaskViewFocus(View newFocus) {
-        if (mSv.useGridLayout()) {
-            // The frame only shows up in the grid layout. It shouldn't show up in the stack
-            // layout including when we're in the split screen.
-            if (newFocus instanceof GridTaskView) {
-                // If the focus goes to a GridTaskView, we show the frame and layout it.
-                int[] location = new int[2];
-                newFocus.getLocationInWindow(location);
-                int thickness = mTaskGridLayoutAlgorithm.getFocusFrameThickness();
-                setTranslationX(location[0] - thickness);
-                setTranslationY(location[1] - thickness);
-                show();
-            } else {
-                // If focus goes to other views, we hide the frame.
-                hide();
-            }
-        }
-    }
-
-    @Override
-    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
-        if (!mSv.useGridLayout()) {
-            return;
-        }
-        if (newFocus == null) {
-            // We're going to touch mode, unset the focus.
-            moveGridTaskViewFocus(null);
-            return;
-        }
-        if (oldFocus == null) {
-            // We're returning from touch mode, set the focus to the previously focused task.
-            final TaskStack stack = mSv.getStack();
-            final int taskCount = stack.getTaskCount();
-            final int k = stack.indexOfTask(mSv.getFocusedTask());
-            final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
-            mSv.setFocusedTask(taskIndexToFocus, false, true);
-        }
-    }
-
-    private void show() {
-        setAlpha(1f);
-    }
-
-    private void hide() {
-        setAlpha(0f);
-    }
-}
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
deleted file mode 100644
index 15c7c87..0000000
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ /dev/null
@@ -1,261 +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.recents.views.lowram;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.LegacyRecentsImpl;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewTransform;
-
-import java.util.ArrayList;
-
-import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
-
-public class TaskStackLowRamLayoutAlgorithm {
-
-    private static final String TAG = "TaskStackLowRamLayoutAlgorithm";
-    private static final float MAX_OVERSCROLL = 0.2f / 0.3f;
-
-    public static final int MAX_LAYOUT_TASK_COUNT = 9;
-    public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME = 2;
-    public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_APP =
-            NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME + 1;
-    private Rect mWindowRect;
-
-    private int mFlingThreshold;
-    private int mPadding;
-    private int mPaddingLeftRight;
-    private int mTopOffset;
-    private int mPaddingEndTopBottom;
-    private Rect mTaskRect = new Rect();
-    private Rect mSystemInsets = new Rect();
-
-    public TaskStackLowRamLayoutAlgorithm(Context context) {
-        reloadOnConfigurationChange(context);
-    }
-
-    public void reloadOnConfigurationChange(Context context) {
-        mPadding = context.getResources()
-                .getDimensionPixelSize(R.dimen.recents_layout_side_margin_phone);
-        mFlingThreshold = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
-    }
-
-    public void initialize(Rect windowRect) {
-        mWindowRect = windowRect;
-        if (mWindowRect.height() > 0) {
-            int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
-            int windowWidth = mWindowRect.width() - mSystemInsets.right - mSystemInsets.left;
-            int width = Math.min(windowWidth, windowHeight) - mPadding * 2;
-            boolean isLandscape = windowWidth > windowHeight;
-            mTaskRect.set(0, 0, width, isLandscape ? width * 2 / 3 : width);
-            mPaddingLeftRight = (windowWidth - mTaskRect.width()) / 2;
-            mPaddingEndTopBottom = (windowHeight - mTaskRect.height()) / 2;
-
-            // Compute the top offset to center tasks in the middle of the screen
-            mTopOffset = (getTotalHeightOfTasks(MAX_LAYOUT_TASK_COUNT) - windowHeight) / 2;
-        }
-    }
-
-    public void setSystemInsets(Rect systemInsets) {
-        mSystemInsets = systemInsets;
-    }
-
-    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
-        RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState();
-        int maxVisible = launchState.launchedFromHome || launchState.launchedFromPipApp
-                    || launchState.launchedWithNextPipApp
-                ? NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME
-                : NUM_TASK_VISIBLE_LAUNCHED_FROM_APP;
-        int visibleCount = Math.min(maxVisible, tasks.size());
-        return new VisibilityReport(visibleCount, visibleCount);
-    }
-
-    public void getFrontOfStackTransform(TaskViewTransform transformOut,
-            TaskStackLayoutAlgorithm stackLayout) {
-        if (mWindowRect == null) {
-            transformOut.reset();
-            return;
-        }
-
-        // Calculate the static task y position 2 tasks after/below the middle/current task
-        int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
-        int bottomOfCurrentTask = (windowHeight + mTaskRect.height()) / 2;
-        int y = bottomOfCurrentTask + mTaskRect.height() + mPadding * 2;
-        fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true);
-    }
-
-    public void getBackOfStackTransform(TaskViewTransform transformOut,
-            TaskStackLayoutAlgorithm stackLayout) {
-        if (mWindowRect == null) {
-            transformOut.reset();
-            return;
-        }
-
-        // Calculate the static task y position 2 tasks before/above the middle/current task
-        int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
-        int topOfCurrentTask = (windowHeight - mTaskRect.height()) / 2;
-        int y = topOfCurrentTask - (mTaskRect.height() + mPadding) * 2;
-        fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true);
-    }
-
-    public TaskViewTransform getTransform(int taskIndex, float stackScroll,
-            TaskViewTransform transformOut, int taskCount, TaskStackLayoutAlgorithm stackLayout) {
-        if (taskCount == 0) {
-            transformOut.reset();
-            return transformOut;
-        }
-        boolean visible = true;
-        int y;
-        if (taskCount > 1) {
-            y = getTaskTopFromIndex(taskIndex) - percentageToScroll(stackScroll);
-
-            // Check visibility from the bottom of the task
-            visible = y + mPadding + getTaskRect().height() > 0;
-        } else {
-            int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
-            y = (windowHeight - mTaskRect.height()) / 2 - percentageToScroll(stackScroll);
-        }
-        fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, visible);
-        return transformOut;
-    }
-
-    /**
-     * Finds the closest task to the scroll percentage in the y axis and returns the percentage of
-     * the task to scroll to.
-     * @param scrollP percentage to find nearest to
-     * @param numTasks number of tasks in recents stack
-     * @param velocity speed of fling
-     */
-    public float getClosestTaskP(float scrollP, int numTasks, int velocity) {
-        int y = percentageToScroll(scrollP);
-
-        int lastY = getTaskTopFromIndex(0) - mPaddingEndTopBottom;
-        for (int i = 1; i < numTasks; i++) {
-            int taskY = getTaskTopFromIndex(i) - mPaddingEndTopBottom;
-            int diff = taskY - y;
-            if (diff > 0) {
-                int diffPrev = Math.abs(y - lastY);
-                boolean useNext = diff > diffPrev;
-                if (Math.abs(velocity) > mFlingThreshold) {
-                    useNext = velocity > 0;
-                }
-                return useNext
-                        ? scrollToPercentage(lastY) : scrollToPercentage(taskY);
-            }
-            lastY = taskY;
-        }
-        return scrollToPercentage(lastY);
-    }
-
-    /**
-     * Convert a scroll value to a percentage
-     * @param scroll a scroll value
-     * @return a percentage that represents the scroll from the total height of tasks
-     */
-    public float scrollToPercentage(int scroll) {
-        return (float) scroll / (mTaskRect.height() + mPadding);
-    }
-
-    /**
-     * Converts a percentage to the scroll value from the total height of tasks
-     * @param p a percentage that represents the scroll value
-     * @return a scroll value in pixels
-     */
-    public int percentageToScroll(float p) {
-        return (int) (p * (mTaskRect.height() + mPadding));
-    }
-
-    /**
-     * Get the min scroll progress for low ram layout. This computes the top position of the
-     * first task and reduce by the end padding to center the first task
-     * @return position of max scroll
-     */
-    public float getMinScrollP() {
-        return getScrollPForTask(0);
-    }
-
-    /**
-     * Get the max scroll progress for low ram layout. This computes the top position of the last
-     * task and reduce by the end padding to center the last task
-     * @param taskCount the amount of tasks in the recents stack
-     * @return position of max scroll
-     */
-    public float getMaxScrollP(int taskCount) {
-        return getScrollPForTask(taskCount - 1);
-    }
-
-    /**
-     * Get the initial scroll value whether launched from home or from an app.
-     * @param taskCount the amount of tasks currently in recents
-     * @param fromHome if launching recents from home or not
-     * @return from home it will return max value and from app it will return 2nd last task
-     */
-    public float getInitialScrollP(int taskCount, boolean fromHome) {
-        if (fromHome) {
-            return getMaxScrollP(taskCount);
-        }
-        if (taskCount < 2) {
-            return 0;
-        }
-        return getScrollPForTask(taskCount - 2);
-    }
-
-    /**
-     * Get the scroll progress for any task
-     * @param taskIndex task index to get the scroll progress of
-     * @return scroll progress of task
-     */
-    public float getScrollPForTask(int taskIndex) {
-        return scrollToPercentage(getTaskTopFromIndex(taskIndex) - mPaddingEndTopBottom);
-    }
-
-    public Rect getTaskRect() {
-        return mTaskRect;
-    }
-
-    public float getMaxOverscroll() {
-        return MAX_OVERSCROLL;
-    }
-
-    private int getTaskTopFromIndex(int index) {
-        return getTotalHeightOfTasks(index) - mTopOffset;
-    }
-
-    private int getTotalHeightOfTasks(int taskCount) {
-        return taskCount * mTaskRect.height() + (taskCount + 1) * mPadding;
-    }
-
-    private void fillStackTransform(TaskViewTransform transformOut, int y, int translationZ,
-            boolean visible) {
-        transformOut.scale = 1f;
-        transformOut.alpha = 1f;
-        transformOut.translationZ = translationZ;
-        transformOut.dimAlpha = 0f;
-        transformOut.viewOutlineAlpha = 1f;
-        transformOut.rect.set(getTaskRect());
-        transformOut.rect.offset(mPaddingLeftRight + mSystemInsets.left, y);
-        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
-        transformOut.visible = visible;
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java
new file mode 100644
index 0000000..1426266
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java
@@ -0,0 +1,52 @@
+/*
+ * 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.plugins;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin to attach custom views under QQS.
+ *
+ * A parent view is provided to the plugin to which they can add Views.
+ * <br>
+ * The parent is a {@link FrameLayout} with same background as QS and 96dp height.
+ *
+ * {@see NPVPluginManager}
+ * {@see status_bar_expanded_plugin_frame}
+ */
+@ProvidesInterface(action = NPVPlugin.ACTION, version = NPVPlugin.VERSION)
+public interface NPVPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_NPV";
+    int VERSION = 1;
+
+    /**
+     * Attach views to the parent.
+     *
+     * @param parent a {@link FrameLayout} to which to attach views. Preferably a root view.
+     * @return a view attached to parent.
+     */
+    View attachToRoot(FrameLayout parent);
+
+    /**
+     * Indicate to the plugin when it is listening (QS expanded)
+     * @param listening
+     */
+    default void setListening(boolean listening) {};
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
index 60435d0..d62c1d4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
@@ -22,7 +22,7 @@
 
 /**
  * Allows for additional sensors to be retrieved from
- * {@link com.android.systemui.util.AsyncSensorManager}.
+ * {@link com.android.systemui.util.sensors.AsyncSensorManager}.
  */
 @ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION)
 public interface SensorManagerPlugin extends Plugin {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 834f4fc..db026ca 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.plugins.qs;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.metrics.LogMaker;
@@ -25,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile.Icon;
 import com.android.systemui.plugins.qs.QSTile.State;
 
+import java.lang.annotation.Retention;
 import java.util.Objects;
 import java.util.function.Supplier;
 
@@ -64,6 +68,14 @@
 
     void destroy();
 
+    /**
+      * return true if the tile supports detail views, and not
+      * only boolean states
+      */
+    default boolean supportsDetailView() {
+        return false;
+    }
+
     CharSequence getTileLabel();
 
     State getState();
@@ -72,6 +84,17 @@
         return logMaker;
     }
 
+    @Retention(SOURCE)
+    @IntDef({COLOR_TILE_ACCENT, COLOR_TILE_RED, COLOR_TILE_BLUE, COLOR_TILE_YELLOW,
+            COLOR_TILE_GREEN})
+    @interface ColorTile {}
+    int COLOR_TILE_ACCENT = 0;
+    int COLOR_TILE_RED = 1;
+    int COLOR_TILE_BLUE = 2;
+    int COLOR_TILE_YELLOW = 3;
+    int COLOR_TILE_GREEN = 4;
+    default void setColor(@ColorTile int color) {}
+
     @ProvidesInterface(version = Callback.VERSION)
     public interface Callback {
         public static final int VERSION = 1;
@@ -118,6 +141,7 @@
         public SlashState slash;
         public boolean handlesLongClick = true;
         public boolean showRippleEffect = true;
+        public int colorActive = -1;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
@@ -137,7 +161,8 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash)
                     || !Objects.equals(other.handlesLongClick, handlesLongClick)
-                    || !Objects.equals(other.showRippleEffect, showRippleEffect);
+                    || !Objects.equals(other.showRippleEffect, showRippleEffect)
+                    || !Objects.equals(other.colorActive, colorActive);
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
@@ -152,6 +177,7 @@
             other.slash = slash != null ? slash.copy() : null;
             other.handlesLongClick = handlesLongClick;
             other.showRippleEffect = showRippleEffect;
+            other.colorActive = colorActive;
             return changed;
         }
 
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 7e03a5c..b6cac13 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -117,7 +117,7 @@
     <string name="kg_pin_accepted" msgid="7637293533973802143">"Kood on õige."</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"Teenus puudub."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"Vaheta sisestusmeetodit"</string>
-    <string name="airplane_mode" msgid="3807209033737676010">"Lennurežiim"</string>
+    <string name="airplane_mode" msgid="3807209033737676010">"Lennukirežiim"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="7246972020562621506">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="6303592361322290145">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
     <string name="kg_prompt_reason_restart_password" msgid="6984641181515902406">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
deleted file mode 100644
index 0c6d57d..0000000
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorBackgroundFloating" />
-    <corners
-        android:topLeftRadius="@dimen/biometric_dialog_corner_size"
-        android:topRightRadius="@dimen/biometric_dialog_corner_size"
-        android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
-        android:bottomRightRadius="@dimen/biometric_dialog_corner_size"/>
-</shape>
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
index 1661bb2..8c7e82f 100644
--- a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
@@ -24,4 +24,5 @@
         android:width="1dp"
         android:color="#66FFFFFF" />
 
+    <solid android:color="#B3000000" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
new file mode 100644
index 0000000..a1006a8
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -0,0 +1,118 @@
+<!--
+  ~ 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.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+        android:id="@+id/title"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="24dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="20sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:paddingHorizontal="24dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="8dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <ImageView
+        android:id="@+id/biometric_icon"
+        android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
+        android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+        android:paddingTop="48dp"
+        android:layout_gravity="center_horizontal"
+        android:scaleType="fitXY" />
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="24dp"
+        android:textSize="12sp"
+        android:gravity="center_horizontal"
+        android:accessibilityLiveRegion="polite"
+        android:textColor="@color/biometric_dialog_gray"/>
+
+    <LinearLayout
+        android:id="@+id/button_bar"
+        android:layout_width="match_parent"
+        android:layout_height="72dip"
+        android:paddingTop="24dp"
+        android:layout_gravity="center_vertical"
+        style="?android:attr/buttonBarStyle"
+        android:orientation="horizontal">
+        <Space android:id="@+id/leftSpacer"
+            android:layout_width="12dp"
+            android:layout_height="match_parent"
+            android:visibility="visible" />
+        <!-- Negative Button -->
+        <Button android:id="@+id/button_negative"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:gravity="center"
+            android:maxLines="2"/>
+        <Space android:id="@+id/middleSpacer"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="visible"/>
+        <!-- Positive Button -->
+        <Button android:id="@+id/button_positive"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Colored"
+            android:gravity="center"
+            android:maxLines="2"
+            android:text="@string/biometric_dialog_confirm"
+            android:visibility="gone"/>
+        <!-- Try Again Button -->
+        <Button android:id="@+id/button_try_again"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Colored"
+            android:gravity="center"
+            android:maxLines="2"
+            android:text="@string/biometric_dialog_try_again"
+            android:visibility="gone"/>
+        <Space android:id="@+id/rightSpacer"
+            android:layout_width="12dip"
+            android:layout_height="match_parent"
+            android:visibility="visible" />
+    </LinearLayout>
+
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
new file mode 100644
index 0000000..be30f21
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.systemui.biometrics.AuthBiometricFaceView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contents"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <include layout="@layout/auth_biometric_contents"/>
+
+</com.android.systemui.biometrics.AuthBiometricFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
new file mode 100644
index 0000000..ce53e27
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.systemui.biometrics.AuthBiometricFingerprintView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contents"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <include layout="@layout/auth_biometric_contents"/>
+
+</com.android.systemui.biometrics.AuthBiometricFingerprintView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml
new file mode 100644
index 0000000..23199aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_container_view.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/biometric_dialog_dim_color"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"/>
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:elevation="@dimen/biometric_dialog_elevation"/>
+
+    <ScrollView
+        android:id="@+id/scrollview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_margin="@dimen/biometric_dialog_border_padding"
+        android:elevation="@dimen/biometric_dialog_elevation"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
deleted file mode 100644
index e687cdf..0000000
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ /dev/null
@@ -1,190 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ImageView
-        android:id="@+id/background"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:scaleType="center" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="bottom"
-        android:background="@color/biometric_dialog_dim_color"
-        android:orientation="vertical">
-
-        <!-- This is not a Space since Spaces cannot be clicked -->
-        <View
-            android:id="@+id/space"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-        <ScrollView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
-                depending on horizontal/portrait orientation -->
-                <View
-                    android:id="@+id/left_space"
-                    android:layout_weight="1"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-                    <LinearLayout
-                        android:id="@+id/dialog"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:orientation="vertical"
-                        android:background="@drawable/biometric_dialog_bg"
-                        android:layout_marginBottom="@dimen/biometric_dialog_border_padding"
-                        android:layout_marginLeft="@dimen/biometric_dialog_border_padding"
-                        android:layout_marginRight="@dimen/biometric_dialog_border_padding">
-
-                        <TextView
-                            android:id="@+id/title"
-                            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:layout_marginTop="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:textSize="20sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <TextView
-                            android:id="@+id/subtitle"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="8dp"
-                            android:layout_marginStart="24dp"
-                            android:layout_marginEnd="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:textSize="16sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <TextView
-                            android:id="@+id/description"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:paddingTop="8dp"
-                            android:textSize="16sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <ImageView
-                            android:id="@+id/biometric_icon"
-                            android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
-                            android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
-                            android:layout_gravity="center_horizontal"
-                            android:layout_marginTop="48dp"
-                            android:scaleType="fitXY" />
-
-                        <TextView
-                            android:id="@+id/error"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:paddingTop="16dp"
-                            android:paddingBottom="24dp"
-                            android:textSize="12sp"
-                            android:gravity="center_horizontal"
-                            android:accessibilityLiveRegion="polite"
-                            android:textColor="@color/biometric_dialog_gray"/>
-
-                        <LinearLayout
-                            android:layout_width="match_parent"
-                            android:layout_height="72dip"
-                            android:paddingTop="24dp"
-                            android:layout_gravity="center_vertical"
-                            style="?android:attr/buttonBarStyle"
-                            android:orientation="horizontal"
-                            android:measureWithLargestChild="true">
-                            <Space android:id="@+id/leftSpacer"
-                                android:layout_width="12dp"
-                                android:layout_height="match_parent"
-                                android:visibility="visible" />
-                            <!-- Negative Button -->
-                            <Button android:id="@+id/button2"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                                android:gravity="center"
-                                android:maxLines="2" />
-                            <Space android:id="@+id/middleSpacer"
-                                android:layout_width="0dp"
-                                android:layout_height="match_parent"
-                                android:layout_weight="1"
-                                android:visibility="visible" />
-                            <!-- Positive Button -->
-                            <Button android:id="@+id/button1"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Colored"
-                                android:gravity="center"
-                                android:maxLines="2"
-                                android:text="@string/biometric_dialog_confirm"
-                                android:visibility="gone"/>
-                            <!-- Try Again Button -->
-                            <Button android:id="@+id/button_try_again"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Colored"
-                                android:gravity="center"
-                                android:maxLines="2"
-                                android:text="@string/biometric_dialog_try_again"
-                                android:visibility="gone"/>
-                            <Space android:id="@+id/rightSpacer"
-                                android:layout_width="12dip"
-                                android:layout_height="match_parent"
-                                android:visibility="visible" />
-                        </LinearLayout>
-                    </LinearLayout>
-
-                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
-                depending on horizontal/portrait orientation -->
-                <View
-                    android:id="@+id/right_space"
-                    android:layout_weight="1"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-            </LinearLayout>
-
-        </ScrollView>
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index 245177c..ca085b6 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -20,6 +20,13 @@
     android:layout_height="@dimen/pip_dismiss_gradient_height"
     android:layout_gravity="bottom|center_horizontal">
 
+    <FrameLayout
+        android:id="@+id/bubble_dismiss_circle"
+        android:layout_width="@dimen/bubble_dismiss_encircle_size"
+        android:layout_height="@dimen/bubble_dismiss_encircle_size"
+        android:layout_gravity="center"
+        android:background="@drawable/bubble_dismiss_circle" />
+
     <LinearLayout
         android:id="@+id/bubble_dismiss_icon_container"
         android:layout_width="wrap_content"
@@ -38,29 +45,5 @@
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:src="@drawable/bubble_dismiss_icon" />
-
-        <TextView
-            android:id="@+id/bubble_dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="9dp"
-            android:layout_marginBottom="9dp"
-            android:layout_marginLeft="8dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
-            android:textColor="@android:color/white"
-            android:shadowColor="@android:color/black"
-            android:shadowDx="-1"
-            android:shadowDy="1"
-            android:shadowRadius="0.01"
-            android:text="@string/bubble_dismiss_text" />
-
     </LinearLayout>
-
-    <FrameLayout
-        android:id="@+id/bubble_dismiss_circle"
-        android:layout_width="@dimen/bubble_dismiss_encircle_size"
-        android:layout_height="@dimen/bubble_dismiss_encircle_size"
-        android:layout_gravity="center"
-        android:alpha="0"
-        android:background="@drawable/bubble_dismiss_circle" />
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index a8eb2914..e2dea45 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -24,7 +24,6 @@
         android:id="@+id/bubble_image"
         android:layout_width="@dimen/individual_bubble_size"
         android:layout_height="@dimen/individual_bubble_size"
-        android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
 </com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml b/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
index 2b2100c..9376b1f 100644
--- a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
+++ b/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
@@ -1,17 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-        android:id="@+id/keyguard_indication_enterprise_disclosure"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:paddingStart="@dimen/keyguard_indication_text_padding"
-        android:paddingEnd="@dimen/keyguard_indication_text_padding"
-        android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
-        android:visibility="gone"/>
-
     <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
         android:id="@+id/keyguard_indication_text"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 8ba4c9c..ba6b695 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -24,6 +24,21 @@
     android:layout_width="match_parent"
     android:background="@drawable/system_bar_background">
 
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_left"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="left|bottom"
+        android:rotation="270"
+        android:visibility="gone"/>
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_right"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="right|bottom"
+        android:rotation="180"
+        android:visibility="gone"/>
+
     <com.android.systemui.statusbar.phone.NavigationBarInflaterView
         android:id="@+id/navigation_inflater"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/qs_carrier_group.xml
index 36f382b..56efb49 100644
--- a/packages/SystemUI/res/layout/qs_carrier_group.xml
+++ b/packages/SystemUI/res/layout/qs_carrier_group.xml
@@ -25,6 +25,17 @@
     android:orientation="horizontal">
 
 
+    <com.android.systemui.util.AutoMarqueeTextView
+        android:id="@+id/no_carrier_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.QS.Status"
+        android:textDirection="locale"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:singleLine="true"
+        android:maxEms="7"
+        android:visibility="gone"/>
+
     <include
         layout="@layout/qs_carrier"
         android:id="@+id/carrier1"
diff --git a/packages/SystemUI/res/layout/qs_tile_detail_text.xml b/packages/SystemUI/res/layout/qs_tile_detail_text.xml
new file mode 100644
index 0000000..bcbf826
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_tile_detail_text.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<!-- use 'dp' instead of 'sp' as we do not want the text to increase
+     if the user scales the font size -->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom|center_horizontal"
+    android:text="..."
+    android:textSize="16dp"
+    android:fontFamily="@*android:string/config_headlineFontFamily"
+    android:singleLine="true"
+    android:visibility="gone"
+    android:paddingBottom="@dimen/qs_tile_detail_padding"
+    android:clickable="false"
+    android:focusable="false" />
+
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index b409c8f..1849068 100644
--- a/packages/SystemUI/res/layout/rounded_corners.xml
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -18,18 +18,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <com.android.systemui.CornerHandleView
-        android:id="@+id/assist_hint_left"
-        android:layout_width="36dp"
-        android:layout_height="36dp"
-        android:layout_gravity="left|top"
-        android:visibility="gone"/>
-    <com.android.systemui.CornerHandleView
-        android:id="@+id/assist_hint_right"
-        android:layout_width="36dp"
-        android:layout_height="36dp"
-        android:layout_gravity="right|bottom"
-        android:visibility="gone"/>
     <ImageView
         android:id="@+id/left"
         android:layout_width="12dp"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 7a94008..e7c6b25 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -111,4 +111,6 @@
         android:visibility="invisible"
         android:background="@drawable/qs_navbar_scrim" />
 
+    <include layout="@layout/status_bar_expanded_plugin_frame"/>
+
 </com.android.systemui.statusbar.phone.NotificationPanelView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
new file mode 100644
index 0000000..4849dfb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/plugin_frame"
+    android:theme="@style/qs_theme"
+    android:layout_width="@dimen/qs_panel_width"
+    android:layout_height="96dp"
+    android:layout_gravity="center_horizontal"
+    android:layout_marginTop="@*android:dimen/quick_qs_total_height"
+    android:layout_marginLeft="@dimen/notification_side_paddings"
+    android:layout_marginRight="@dimen/notification_side_paddings"
+    android:visibility="gone"
+    android:background="@drawable/qs_background_primary"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index a914930..9716a00 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -44,7 +44,7 @@
     </com.android.systemui.statusbar.BackDropView>
 
     <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
+        android:id="@+id/scrim_for_bubble"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
@@ -56,6 +56,14 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
     <include layout="@layout/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 538fdce..4d451bc 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Kanselleer"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bevestig"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Probeer weer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Leë gebied; tik om stawing te kanselleer"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tik om stawing te kanselleer"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probeer asseblief weer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Soek tans jou gesig"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gesig is gestaaf"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bevestig"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tik op Bevestig om te voltooi"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Gestaaf"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Raak die vingerafdruksensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Vingerafdrukikoon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Soek tans vir jou …"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index a3ff5b4..4d0a62e 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ይቅር"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"አረጋግጥ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"እንደገና ይሞክሩ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ባዶ ክልል፣ ፈቃድን ለመሰረዝ መታ ያድርጉ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ማረጋገጥን ለመሰረዝ መታ ያድርጉ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"እባክዎ እንደገና ይሞክሩ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"መልክዎን በመፈለግ ላይ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"መልክ ተረጋግጧል"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ተረጋግጧል"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ለማጠናቀቅ አረጋግጥን መታ ያድርጉ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"የተረጋገጠ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"የጣት አሻራ ዳሳሹን ይንኩ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"የጣት አሻራ አዶ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"እርስዎን በመፈለግ ላይ…"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 529ca5c..787fc8d 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"إلغاء"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تأكيد"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"إعادة المحاولة"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"منطقة خالية، يُرجى النقر لإلغاء المصادقة."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"انقر لإلغاء المصادقة."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"يُرجى إعادة المحاولة."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"جارٍ البحث عن وجهك"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"تمّت مصادقة الوجه."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تمّ التأكيد."</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"يمكنك النقر على \"تأكيد\" لإكمال المهمة."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"مصادقة"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"المس زر استشعار بصمة الإصبع"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"رمز بصمة الإصبع"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"جارٍ البحث عن وجهك…"</string>
@@ -555,8 +556,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. مرّر الشاشة بسرعة للأعلى مع الاستمرار لإزالة تثبيت الشاشة."</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"نظرة عامة\" لإزالة التثبيت."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"الشاشة الرئيسية\" لإزالة التثبيت."</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"لإزالة تثبيت هذه الشاشة، يمكنك أن تلمس مع الاستمرار زرّي \"رجوع\" و\"نظرة عامة\"."</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"لإزالة تثبيت هذه الشاشة، يمكنك أن تلمس مع الاستمرار زرّي \"رجوع\" و\"الشاشة الرئيسية\"."</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"لإزالة تثبيت هذه الشاشة، يمكنك النقر مع الاستمرار على زرّي \"الرجوع\" و\"النظرة العامة\"."</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"لإزالة تثبيت هذه الشاشة، يمكنك النقر مع الاستمرار على زرّي \"الرجوع\" و\"الشاشة الرئيسية\"."</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"مرّر الشاشة بسرعة للأعلى مع الاستمرار لإزالة تثبيت الشاشة."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"حسنًا"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"لا، شكرًا"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 7d8ba64..d7feb0c 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"বাতিল কৰক"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"নিশ্চিত কৰক"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"আকৌ চেষ্টা কৰক"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"এলেকা খালী আছে, বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ বাতিল কৰিবলৈ টিপক"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ বাতিল কৰিবলৈ টিপক"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"অনুগ্ৰহ কৰি আকৌ চেষ্টা কৰক"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"আপোনাৰ মুখমণ্ডল বিচাৰি থকা হৈছে"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"মুখমণ্ডলৰ বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰা হ’ল"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"নিশ্চিত কৰিলে"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"সম্পূৰ্ণ কৰিবলৈ নিশ্চিত কৰক-ত টিপক"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰা হ’ল"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো স্পৰ্শ কৰক"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ফিংগাৰপ্ৰিণ্ট আইকন"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"আপোনাৰ মুখমণ্ডল বিচাৰি আছে…"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 5ae4d2d..f6cd5dc 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ləğv et"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Təsdiq"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Yenidən cəhd edin"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Region boşdur, identifikasiyanı ləğv etmək üçün toxunun"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Doğrulanmanı ləğv etmək üçün toxunun"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Yenidən cəhd edin"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Üzünüz axtarılır"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Üz doğrulandı"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Təsdiqləndi"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tamamlamaq üçün \"Təsdiq edin\" seçiminə toxunun"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Doğrulandı"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Barmaq izi sensoruna klikləyin"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Barmaq izi ikonası"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Siz axtarılırsınız…"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 95346e4..2196c15 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Otkaži"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Probaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazna oblast, dodirnite da biste otkazali potvrdu identiteta"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da biste otkazali potvrdu identiteta"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traži se vaše lice"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je potvrđeno"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi da biste završili"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Identitet je potvrđen"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor za otisak prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otiska prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3674564..06ee3e1 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Скасаваць"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Пацвердзіць"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Паўтарыць спробу"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Пустая вобласць. Націсніце, каб скасаваць аўтэнтыфікацыю"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Націсніце, каб скасаваць аўтэнтыфікацыю"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Паўтарыце спробу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ідзе пошук твару"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Твар распазнаны"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Пацверджана"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Націсніце \"Пацвердзіць\", каб завяршыць"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Распазнана"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Дакраніцеся да сканера адбіткаў пальцаў"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок адбіткаў пальцаў"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ідзе пошук вашага твару…"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1618e05..6f37a75 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Отказ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потвърждаване"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Нов опит"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празна област. Докоснете, за да анулирате удостоверяването"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Докоснете, за да анулирате удостоверяването"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Моля, опитайте отново"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Лицето ви се търси"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицето е удостоверено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потвърдено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Докоснете „Потвърждаване“ за завършване"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Удостоверено"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Докоснете сензора за отпечатъци"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона за отпечатък"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Търсим ви…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Ппоказване на по-малко спешните известия по-долу"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Докоснете отново, за да отворите"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Прекарайте пръст нагоре, за да отключите"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Прекарайте пръст нагоре, за да опитате отново"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Плъзнете бързо нагоре, за да опитате отново"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Това устройство се управлява от организацията ви"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Това устройство се управлява от <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Плъзнете с пръст от иконата, за да използвате телефона"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 6b7561b..9c33f2b 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"বাতিল করুন"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"কনফার্ম করুন"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"আবার চেষ্টা করুন"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"জায়গাটি খালি, যাচাইকরণ বাতিল করতে এখানে ট্যাপ করুন"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"যাচাইকরণ বাতিল করতে ট্যাপ করুন"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"আবার চেষ্টা করুন"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"আপনার ফেস খোঁজা হচ্ছে"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ফেস যাচাই করা হয়েছে"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"কনফার্ম করা হয়েছে"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"সম্পূর্ণ করতে \'কনফার্ম করুন\' বোতামে ট্যাপ করুন"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"প্রমাণীকৃত"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"আঙ্গুলের ছাপের সেন্সর স্পর্শ করুন"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"আঙ্গুলের ছাপের আইকন"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"আপনার জন্য খোঁজা হচ্ছে…"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index c22ddfd..2d77626 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Otkaži"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Pokušaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Područje je prazno. Dodirnite da otkažete autentifikaciju."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da otkažete autentifikaciju"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pokušajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traženje vašeg lica"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je provjereno"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi da završite"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentificirano"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor za otisak prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona za otisak prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index d4147ba..ee16c58 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel·la"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirma"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Torna-ho a provar"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zona buida; toca per cancel·lar l\'autenticació"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca per cancel·lar l\'autenticació"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Torna-ho a provar"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"S\'està cercant la teva cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Cara autenticada"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirma per completar"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticat"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor d\'empremtes digitals"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona d\'empremta digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"S\'està cercant la teva cara…"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 87aaa94..bed63db 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Zrušit"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdit"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Zkusit znovu"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prázdná oblast, klepnutím ověření zrušíte"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Klepnutím zrušíte ověření"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Zkuste to prosím znovu"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Vyhledávání obličeje"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Obličej byl ověřen"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrzeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ověření dokončíte klepnutím na Potvrdit"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Ověřeno"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotkněte se snímače otisků prstů"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otisku prstu"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hledáme vás…"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 6a97ee6..45ef5b1 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuller"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekræft"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Prøv igen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tomt område. Tryk for at annullere godkendelsen."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tryk for at annullere godkendelsen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Prøv igen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Søger efter dit ansigt"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansigtet er godkendt"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekræftet"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tryk på Bekræft for at udføre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Godkendt"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sæt fingeren på fingeraftrykslæseren"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon for fingeraftryk"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Forsøger at finde dig…"</string>
@@ -215,7 +216,7 @@
     <skip />
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notifikationen er annulleret."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Notifikationspanel."</string>
-    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hurtige indstillinger."</string>
+    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Kvikmenu."</string>
     <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Låseskærm."</string>
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"Indstillinger"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Oversigt."</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Dette fastholder skærmen i visningen, indtil du frigør den. Tryk på Tilbage, og hold fingeren nede for at frigøre skærmen."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Dette fastholder skærmen i visningen, indtil du frigør den. Hold Startskærm nede for at frigøre skærmen."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Hold knapperne Tilbage og Oversigt nede for at frigøre skærmen"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Hold knapperne Tilbage og Startskærm nede for at frigøre skærmen"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Hold knapperne Tilbage og Hjem nede for at frigøre skærmen"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Stryg opad, og hold fingeren nede for at frigøre denne skærm"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK, det er forstået"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tak"</string>
@@ -588,7 +589,7 @@
     <string name="system_ui_tuner" msgid="708224127392452018">"System UI Tuner"</string>
     <string name="show_battery_percentage" msgid="5444136600512968798">"Vis procent for det indbyggede batteri"</string>
     <string name="show_battery_percentage_summary" msgid="3215025775576786037">"Vis procenttallet for batteriniveauet i ikonet for statusbjælken, når der ikke oplades"</string>
-    <string name="quick_settings" msgid="10042998191725428">"Hurtige indstillinger"</string>
+    <string name="quick_settings" msgid="10042998191725428">"Kvikmenu"</string>
     <string name="status_bar" msgid="4877645476959324760">"Statusbjælke"</string>
     <string name="overview" msgid="4018602013895926956">"Oversigt"</string>
     <string name="demo_mode" msgid="2532177350215638026">"Demotilstand for systemets brugerflade"</string>
@@ -604,7 +605,7 @@
     <string name="zen_alarm_warning" msgid="444533119582244293">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template" msgid="3980063409350522735">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template_far" msgid="4242179982586714810">"på <xliff:g id="WHEN">%1$s</xliff:g>"</string>
-    <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Hurtige indstillinger <xliff:g id="TITLE">%s</xliff:g>."</string>
+    <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Kvikmenu <xliff:g id="TITLE">%s</xliff:g>."</string>
     <string name="accessibility_status_bar_hotspot" msgid="4099381329956402865">"Hotspot"</string>
     <string name="accessibility_managed_profile" msgid="6613641363112584120">"Arbejdsprofil"</string>
     <string name="tuner_warning_title" msgid="7094689930793031682">"Sjovt for nogle, men ikke for alle"</string>
@@ -617,8 +618,8 @@
     <string name="activity_not_found" msgid="348423244327799974">"Applikationen er ikke installeret på din enhed."</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Vis sekunder"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
-    <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
-    <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
+    <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Kvikmenu"</string>
+    <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Kvikmenu"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
     <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
@@ -813,15 +814,15 @@
     <string name="accessibility_qs_edit_remove_tile" msgid="7484493384665907197">"Fjern <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add" msgid="3520406665865985109">"Føj <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_move" msgid="3108103090006972938">"Flyt <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
-    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj for Hurtige indstillinger."</string>
+    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj til Kvikmenu."</string>
     <string name="accessibility_desc_notification_icon" msgid="8352414185263916335">"<xliff:g id="ID_1">%1$s</xliff:g>-notifikation: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="dock_forced_resizable" msgid="5914261505436217520">"Appen fungerer muligvis ikke i opdelt skærm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"Appen understøtter ikke opdelt skærm."</string>
     <string name="forced_resizable_secondary_display" msgid="4230857851756391925">"Appen fungerer muligvis ikke på sekundære skærme."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="7793821742158306742">"Appen kan ikke åbnes på sekundære skærme."</string>
     <string name="accessibility_quick_settings_settings" msgid="6132460890024942157">"Åbn Indstillinger."</string>
-    <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Hurtige indstillinger."</string>
-    <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Hurtige indstillinger."</string>
+    <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Kvikmenu."</string>
+    <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Kvikmenu."</string>
     <string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Alarmen er indstillet."</string>
     <string name="accessibility_quick_settings_user" msgid="1567445362870421770">"Logget ind som <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="data_connection_no_internet" msgid="4503302451650972989">"Intet internet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index e1fd8db..1deceb1 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Abbrechen"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bestätigen"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Noch einmal versuchen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Feld \"Region\" ist leer, zum Abbrechen der Authentifizierung tippen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Zum Abbrechen der Authentifizierung tippen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Noch einmal versuchen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Nach deinem Gesicht wird gesucht"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gesicht authentifiziert"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bestätigt"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Zum Abschließen auf \"Bestätigen\" tippen"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifiziert"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Berühre den Fingerabdrucksensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerabdruck-Symbol"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Wir suchen nach dir…"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index dfb5edf..2b57ab5 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ακύρωση"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Επιβεβαίωση"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Δοκιμάστε ξανά"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Κενή περιοχή, πατήστε για ακύρωση ελέγχου ταυτότητας"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Πατήστε για ακύρωση του ελέγχου ταυτότητας"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Δοκιμάστε ξανά"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Αναζήτηση για το πρόσωπό σας"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Έγινε έλεγχος ταυτότητας προσώπου"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Επιβεβαιώθηκε"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Πατήστε Επιβεβαίωση για ολοκλήρωση"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Ολοκληρώθηκε ο έλεγχος ταυτότητας"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Αγγίξτε τον αισθητήρα δακτυλικών αποτυπωμάτων"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Εικονίδιο δακτυλικών αποτυπωμάτων"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Αναζήτηση για εσάς…"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 938cb0d..fe19e7c 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 139215aa..32469f2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎Cancel‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Confirm‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎Try again‎‏‎‎‏‎"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎Empty region, tap to cancel authentication‎‏‎‎‏‎"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎Tap to cancel authentication‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎Please try again‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎Looking for your face‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎Face authenticated‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎Confirmed‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎Tap Confirm to complete‎‏‎‎‏‎"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎Authenticated‎‏‎‎‏‎"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎Touch the fingerprint sensor‎‏‎‎‏‎"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎Fingerprint icon‎‏‎‎‏‎"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎Looking for you…‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6adf6d5..f29aa1e 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Volver a intentarlo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Espacio vacío; presiona para cancelar la autenticación"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Presiona para cancelar la autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vuelve a intentarlo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando tu rostro"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Se autenticó el rostro"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmado"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Presiona Confirmar para completarla"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor de huellas digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícono de huella digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Autenticando tu rostro…"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index c4f3445..3088098 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Reintentar"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zona vacía. Toca para cancelar la autenticación."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca para cancelar la autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vuelve a intentarlo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando tu cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Cara autenticada"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirmar para completar la acción"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Se ha autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor de huellas digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icono de huella digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Buscando tu cara…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Aplicaciones recientes."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Inicio."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Mantén pulsado el botón Atrás y el de aplicaciones recientes para dejar de fijar esta pantalla"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Mantén pulsado el botón Atrás y el de Inicio para dejar de fijar esta pantalla"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Mantén pulsados los botones Atrás e Inicio para dejar de fijar esta pantalla"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para dejar de fijar esta pantalla, desliza el dedo hacia arriba y mantenla pulsada"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index a8ad1ec..51226ab 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Tühista"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Kinnita"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Proovi uuesti"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tühi piirkond, puudutage autentimise tühistamiseks"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Puudutage autentimise tühistamiseks"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Proovige uuesti"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Teie näo vaatamine"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Nägu on autenditud"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Kinnitatud"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Lõpuleviimiseks puudutage nuppu Kinnita"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenditud"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Puudutage sõrmejäljeandurit"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Sõrmejälje ikoon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Otsitakse teid …"</string>
@@ -192,7 +193,7 @@
     <string name="not_default_data_content_description" msgid="9194667237765917844">"Ei ole andmeside kasutamiseks seadistatud"</string>
     <string name="cell_data_off" msgid="1051264981229902873">"Väljas"</string>
     <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Jagamine Bluetoothiga."</string>
-    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Lennurežiim."</string>
+    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Lennukirežiim."</string>
     <string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN on sees."</string>
     <string name="accessibility_no_sims" msgid="3957997018324995781">"SIM-kaarti pole."</string>
     <string name="carrier_network_change_mode" msgid="8149202439957837762">"Operaatori võrku muudetakse"</string>
@@ -229,7 +230,7 @@
     <string name="accessibility_quick_settings_airplane_off" msgid="7786329360056634412">"Lennurežiim on väljas."</string>
     <string name="accessibility_quick_settings_airplane_on" msgid="6406141469157599296">"Lennurežiim on sees."</string>
     <string name="accessibility_quick_settings_airplane_changed_off" msgid="66846307818850664">"Lennurežiim on välja lülitatud."</string>
-    <string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"Lennurežiim on sisse lülitatud."</string>
+    <string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"Lennukirežiim on sisse lülitatud."</string>
     <string name="accessibility_quick_settings_dnd_none_on" msgid="2960643943620637020">"täielik vaikus"</string>
     <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3357131899365865386">"ainult alarmid"</string>
     <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"Mitte segada."</string>
@@ -597,7 +598,7 @@
     <string name="status_bar_ethernet" msgid="5044290963549500128">"Ethernet"</string>
     <string name="status_bar_alarm" msgid="8536256753575881818">"Äratus"</string>
     <string name="status_bar_work" msgid="6022553324802866373">"Tööprofiil"</string>
-    <string name="status_bar_airplane" msgid="7057575501472249002">"Lennurežiim"</string>
+    <string name="status_bar_airplane" msgid="7057575501472249002">"Lennukirežiim"</string>
     <string name="add_tile" msgid="2995389510240786221">"Paani lisamine"</string>
     <string name="broadcast_tile" msgid="3894036511763289383">"Paani ülekandmine"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Kuulete järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g> vaid siis, kui lülitate selle enne seda välja"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 799d773..0d2863e 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Utzi"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Berretsi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Saiatu berriro"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Hutsik dago eremua. Sakatu autentifikatzeari uzteko."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Sakatu hau autentifikazioa bertan behera uzteko"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Saiatu berriro"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Aurpegia bilatzen"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Autentifikatu da aurpegia"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Berretsita"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Amaitzeko, sakatu \"Berretsi\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikatuta"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sakatu hatz-marken sentsorea"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Hatz-markaren ikonoa"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Zure bila…"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index eeb30a0..e649d80 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -28,15 +28,15 @@
     <string name="battery_low_percent_format" msgid="2900940511201380775">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است"</string>
     <string name="battery_low_percent_format_hybrid" msgid="6838677459286775617">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> باقی مانده، براساس اشتفاده شما حدود <xliff:g id="TIME">%2$s</xliff:g> باقی مانده است"</string>
     <string name="battery_low_percent_format_hybrid_short" msgid="9025795469949145586">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> باقی مانده، حدود <xliff:g id="TIME">%2$s</xliff:g> باقی مانده است"</string>
-    <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. بهینه‌سازی باتری روشن است."</string>
+    <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. «بهینه‌سازی باتری» روشن است."</string>
     <string name="invalid_charger" msgid="2741987096648693172">"‏ازطریق USB شارژ نمی‌شود. از شارژر ارائه‌شده با دستگاه استفاده کنید."</string>
     <string name="invalid_charger_title" msgid="2836102177577255404">"‏ازطریق USB شارژ نمی‌شود"</string>
     <string name="invalid_charger_text" msgid="6480624964117840005">"از شارژر ارائه‌شده با دستگاه استفاده کنید"</string>
     <string name="battery_low_why" msgid="4553600287639198111">"تنظیمات"</string>
-    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"بهینه‌سازی باتری روشن شود؟"</string>
-    <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"درباره بهینه‌سازی باتری"</string>
+    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"«بهینه‌سازی باتری» روشن شود؟"</string>
+    <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"درباره «بهینه‌سازی باتری»"</string>
     <string name="battery_saver_confirmation_ok" msgid="7507968430447930257">"روشن کردن"</string>
-    <string name="battery_saver_start_action" msgid="8187820911065797519">"بهینه‌سازی باتری را روشن کنید"</string>
+    <string name="battery_saver_start_action" msgid="8187820911065797519">"«بهینه‌سازی باتری» را روشن کنید"</string>
     <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"تنظیمات"</string>
     <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"Wi-Fi"</string>
     <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"چرخش خودکار صفحه"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"لغو"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تأیید"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"امتحان مجدد"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"برای لغو احراز هویت، در قسمت خالی ضربه بزنید"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"برای لغو راستی‌آزمایی ضربه بزنید"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"لطفاً دوباره امتحان کنید"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"درحال جستجوی چهره"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"چهره احراز هویت شد"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تأیید شد"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"برای تکمیل، روی تأیید ضربه بزنید"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"راستی‌آزمایی‌شده"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"حسگر اثر انگشت را لمس کنید"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"نماد اثر انگشت"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"درحال جستجوی شما…"</string>
@@ -450,9 +451,9 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"کاربر حذف شود؟"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"همه برنامه‌ها و داده‌های این کاربر حذف می‌شود."</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"حذف"</string>
-    <string name="battery_saver_notification_title" msgid="8614079794522291840">"بهینه‌سازی باتری روشن است"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"«بهینه‌سازی باتری» روشن است"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"عملکرد و اطلاعات پس‌زمینه را کاهش می‌دهد"</string>
-    <string name="battery_saver_notification_action_text" msgid="132118784269455533">"بهینه‌سازی باتری را خاموش کنید"</string>
+    <string name="battery_saver_notification_action_text" msgid="132118784269455533">"«بهینه‌سازی باتری» را خاموش کنید"</string>
     <string name="media_projection_dialog_text" msgid="8585357687598538511">"هنگام ضبط یا ارسال محتوا، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> می‌تواند هرگونه اطلاعات حساس را (مانند صوت، گذرواژه، اطلاعات پرداخت، عکس و پیام) که روی صفحه‌تان نشان داده می‌شود یا از دستگاهتان پخش می‌شود ضبط کند."</string>
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"هنگام ضبط یا ارسال محتوا، ارائه‌دهنده خدمات این عملکرد می‌تواند هرگونه اطلاعات حساس (مانند صوت، گذرواژه، اطلاعات پرداخت، عکس و پیام) را که روی صفحه‌تان نشان داده می‌شود یا از دستگاهتان پخش می‌شود ضبط کند."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"افشای اطلاعات حساس درحین ارسال/ضبط محتوا"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 7be438a..e195754 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Peruuta"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Vahvista"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Yritä uudelleen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tyhjä alue, napauta sitä peruuttaaksesi tunnistuksen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Peruuta todennus napauttamalla"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Yritä uudelleen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Kasvojasi katsotaan"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Kasvot tunnistettu"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Vahvistettu"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Valitse lopuksi Vahvista"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Todennettu"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Kosketa sormenjälkitunnistinta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Sormenjälkikuvake"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Etsitään kasvoja…"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index e8e4232..4f28efa 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuler"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmer"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Réessayer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Région vide, touchez l\'écran pour annuler l\'authentification"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Touchez ici pour annuler l\'authentification"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Veuillez réessayer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"L\'appareil recherche votre visage…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Visage authentifié"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmé"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Touchez Confirmer pour terminer"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifié"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touchez le capteur d\'empreintes digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icône d\'empreinte digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Recherche de votre visage…"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3bc9378..a51ebb1 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuler"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmer"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Réessayer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zone vide : appuyez pour annuler l\'authentification"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Appuyer pour annuler l\'authentification"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Veuillez réessayer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Recherche de votre visage…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Visage authentifié"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmé"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Appuyez sur \"Confirmer\" pour terminer"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifié"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Appuyez sur le lecteur d\'empreinte digitale"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icône d\'empreinte digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Recherche de votre visage…"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 15d9c2b..7a7ebf2 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar de novo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"A rexión está baleira; tócaa para cancelar a autenticación"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca para cancelar a autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Téntao de novo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando a túa cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Autenticouse a cara"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirmar para completar o proceso"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca o sensor de impresión dixital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona de impresión dixital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Buscándote…"</string>
@@ -599,7 +600,7 @@
     <string name="status_bar_work" msgid="6022553324802866373">"Perfil de traballo"</string>
     <string name="status_bar_airplane" msgid="7057575501472249002">"Modo avión"</string>
     <string name="add_tile" msgid="2995389510240786221">"Engade un atallo"</string>
-    <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de emisión"</string>
+    <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de difusión"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g> a menos que desactives esta opción antes desa hora"</string>
     <string name="zen_alarm_warning" msgid="444533119582244293">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template" msgid="3980063409350522735">"ás <xliff:g id="WHEN">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 1a15cf7..10aba24 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"રદ કરો"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"કન્ફર્મ કરો"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ફરી પ્રયાસ કરો"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ખાલી વિસ્તાર, પ્રમાણીકરણ રદ કરવા માટે ટૅપ કરો"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"પ્રમાણીકરણ રદ કરવા માટે ટૅપ કરો"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"કૃપા કરી ફરી પ્રયાસ કરો"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"તમારો ચહેરો શોધી રહ્યાં છીએ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ચહેરાનું પ્રમાણીકરણ થયું"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"પુષ્ટિ કરી"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"પરીક્ષણ પૂર્ણ કરવા કન્ફર્મ કરોને ટૅપ કરો"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"પ્રમાણિત"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ફિંગરપ્રિન્ટના સેન્સરને સ્પર્શ કરો"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ફિંગરપ્રિન્ટનું આઇકન"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"તમારા માટે શોધી રહ્યાં છે..."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 3c18b0d..9ce8e7d 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द करें"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"पुष्टि करें"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"फिर से कोशिश करें"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"कोई चेहरा नहीं मिला, टैप करके पुष्टि की प्रक्रिया रद्द करें"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"पुष्टि की प्रक्रिया रद्द करने के लिए टैप करें"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया फिर से कोशिश करें"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"आपके चेहरे की पुष्टि की जा रही है"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"चेहरे की पुष्टि हो गई"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"पुष्टि हो गई"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"\'पुष्टि करें\' पर टैप करके पूरा करें"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"पुष्टि हो गई"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फ़िंगरप्रिंट सेंसर को छुएं"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फ़िंगरप्रिंट आइकॉन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"आपको पहचान रहा है…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"कम अत्यावश्यक सूचनाएं नीचे दी गई हैं"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"खोलने के लिए फिर से टैप करें"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"खोलने के लिए ऊपर स्वाइप करें"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"फिर से कोशिश करने के लिए स्वाइप करें"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"फिर से कोशिश करने के लिए ऊपर की ओर स्वाइप करें"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"इस डिवाइस का प्रबंधन आपका संगठन करता है"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"इस डिवाइस के प्रबंधक <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> हैं"</string>
     <string name="phone_hint" msgid="4872890986869209950">"फ़ोन के लिए आइकॉन से स्वाइप करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 052a0d2..9c9b0b0 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Odustani"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Pokušaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazno područje, dodirnite da biste otkazali autentifikaciju"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da biste otkazali autentifikaciju"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pokušajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traženje lica"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je autentificirano"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi za dovršetak"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentičnost provjerena"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor otiska prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otiska prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5f47e20..99a049e 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Mégse"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Megerősítés"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Újrapróbálkozás"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Üres régió. Koppintson a hitelesítés visszavonásához"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Koppintson a hitelesítés visszavonásához"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Próbálja újra"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Arc keresése"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Arc hitelesítve"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Megerősítve"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Koppintson a Megerősítés lehetőségre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Hitelesítve"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Érintse meg az ujjlenyomat-érzékelőt"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ujjlenyomat ikonja"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Keresem az Ön arcát…"</string>
@@ -647,7 +648,7 @@
     <string name="inline_minimize_button" msgid="966233327974702195">"Kis méret"</string>
     <string name="inline_silent_button_silent" msgid="5315879183296940969">"Néma"</string>
     <string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"Néma megjelenítés"</string>
-    <string name="inline_silent_button_alert" msgid="6008435419895088034">"Értesítések"</string>
+    <string name="inline_silent_button_alert" msgid="6008435419895088034">"Figyelemfelkeltő"</string>
     <string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"Értesítések folytatása"</string>
     <string name="inline_turn_off_notifications" msgid="8635596135532202355">"Az értesítések kikapcsolása"</string>
     <string name="inline_keep_showing_app" msgid="1723113469580031041">"Továbbra is megjelenjenek az alkalmazás értesítései?"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4d19cec..9ac0afb 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -55,12 +55,12 @@
     <string name="label_view" msgid="6304565553218192990">"Դիտել"</string>
     <string name="always_use_device" msgid="4015357883336738417">"Միշտ բացել <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածը, երբ <xliff:g id="USB_DEVICE">%2$s</xliff:g> լրասարքը միացված է"</string>
     <string name="always_use_accessory" msgid="3257892669444535154">"Միշտ բացել <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածը, երբ <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> լրասարքը միացված է"</string>
-    <string name="usb_debugging_title" msgid="4513918393387141949">"Թույլատրե՞լ USB-ի կարգաբերումը:"</string>
+    <string name="usb_debugging_title" msgid="4513918393387141949">"Թույլատրե՞լ USB-ով վրիպազերծումը"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Համակարգչի RSA-ի բանալի մատնահետքն է`\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Միշտ թույլատրել այս համակարգչից"</string>
     <string name="usb_debugging_allow" msgid="2272145052073254852">"Թույլատրել"</string>
-    <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB վրիպազերծումը արգելված է"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ընթացիկ հաշվի օգտատերը չի կարող միացնել USB վրիպազերծումը: Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշվով:"</string>
+    <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB-ով վրիպազերծումը թույլատրված չէ"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ընթացիկ հաշվի միջոցով չեք կարող միացնել USB-ով վրիպազերծումը: Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշիվ:"</string>
     <string name="usb_contaminant_title" msgid="206854874263058490">"USB միացքն անջատված է"</string>
     <string name="usb_contaminant_message" msgid="7379089091591609111">"USB միացքն անջատվել է, որպեսզի ձեր սարքը չթրջվի կամ չաղտոտվի: Այժմ USB միացքի միջոցով հնարավոր չէ միացնել այլ սարքեր:\n\nԴուք ծանուցում կստանաք, երբ այն նորից անվտանգ լինի օգտագործել:"</string>
     <string name="usb_port_enabled" msgid="7906141351687694867">"USB միացքը միացվել է՝ լիցքավորիչներն ու լրասարքերը հայտնաբերելու համար"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Չեղարկել"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Հաստատել"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Նորից փորձել"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Ոչինչ չկա։ Հպեք՝ նույնականացումը չեղարկելու համար։"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Հպեք՝ նույնականացումը չեղարկելու համար"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Նորից փորձեք"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Դեմքի նույնականացում"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Դեմքը ճանաչվեց"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Հաստատվեց"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ավարտելու համար հպեք «Հաստատել»"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Նույնականացված է"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Հպեք մատնահետքի սկաներին"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Մատնահետքի պատկերակ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Դեմքի ճանաչում…"</string>
@@ -539,12 +540,12 @@
     <string name="accessibility_output_chooser" msgid="8185317493017988680">"Փոխել արտածման սարքը"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string>
     <string name="screen_pinning_description" msgid="8909878447196419623">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները:"</string>
-    <string name="screen_pinning_description_recents_invisible" msgid="8281145542163727971">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք Հետ և գլխավոր էկրանի կոճակները:"</string>
+    <string name="screen_pinning_description_recents_invisible" msgid="8281145542163727971">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք «Հետ» և «Գլխավոր էկրան» կոճակները"</string>
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար մատը սահեցրեք վեր և պահեք։"</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Համատեսք կոճակը:"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք գլխավոր էկրանի կոճակը:"</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Էկրանն ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Էկրանն ապամրացնելու համար հպեք և պահեք Հետ և գլխավոր էկրանի կոճակները"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Էկրանն ապամրացնելու համար հպեք և պահեք «Հետ» և «Գլխավոր էկրան» կոճակները"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Էկրանն ապամրացնելու համար մատը սահեցրեք վերև և պահեք"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Եղավ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ոչ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index b18beed..443e3b3 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Batal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Konfirmasi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Coba lagi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Area kosong. Ketuk untuk membatalkan autentikasi"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ketuk untuk membatalkan autentikasi"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Coba lagi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Mencari wajah Anda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Wajah diautentikasi"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Dikonfirmasi"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ketuk Konfirmasi untuk menyelesaikan"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Diautentikasi"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sentuh sensor sidik jari"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon sidik jari"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Mencari wajah Anda…"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 129319d..f481e48 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Hætta við"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Staðfesta"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Reyna aftur"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Autt rými, ýttu til að hætta við greiningu"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ýttu til að hætta við auðkenningu"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Reyndu aftur"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Leitar að andliti þínu"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Andlit staðfest"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Staðfest"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ýttu á „Staðfesta“ til að ljúka"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Auðkennt"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Snertu fingrafaralesarann"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingrafaratákn"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Leitar að þér ..."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a9427e5..718608a 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annulla"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Conferma"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Riprova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Spazio vuoto, tocca per annullare l\'autenticazione"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tocca per annullare l\'autenticazione"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Riprova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ricerca del tuo volto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Volto autenticato"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confermato"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tocca Conferma per completare"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticazione eseguita"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Tocca il sensore di impronte digitali"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona dell\'impronta digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"In attesa del volto…"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 52d4a13..606a097 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ביטול"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"אישור"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ניסיון נוסף"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"אזור ריק, יש להקיש עליו כדי לבטל את האימות"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"יש להקיש כדי לבטל את האימות"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"עליך לנסות שוב"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"המערכת מחפשת את הפנים שלך"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"זיהוי הפנים בוצע"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"מאושר"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"יש להקיש על \'אישור\' לסיום"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"מאומת"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"יש לגעת בחיישן טביעות האצבע"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"סמל טביעת אצבע"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"מחפש אותך…"</string>
@@ -463,7 +464,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"בזמן הקלטה או העברה, השירות שמספק את הפונקציה הזו יכול לקלוט מידע רגיש שמוצג במסך או מופעל מהמכשיר שלך, כולל מידע רגיש כמו אודיו, סיסמאות, פרטי תשלום, תמונות והודעות."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"חשיפת מידע רגיש בזמן העברה/הקלטה"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"אל תציג שוב"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"נקה הכל"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"ניקוי הכל"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"ניהול"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"התראות שקטות"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"ניקוי כל ההתראות השקטות"</string>
@@ -625,7 +626,7 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"הצג שניות בשעון בשורת הסטטוס. פעולה זו עשויה להשפיע על אורך חיי הסוללה."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
     <string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
-    <string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
+    <string name="experimental" msgid="6198182315536726162">"ניסיוני"</string>
     <string name="enable_bluetooth_title" msgid="5027037706500635269">"‏האם להפעיל את ה-Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"הפעל"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 05f254f..0bc500c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"キャンセル"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"再試行"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白の領域をタップすると、認証をキャンセルできます"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"タップすると認証をキャンセルします"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"もう一度お試しください"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"顔を認証中です"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"顔を認証しました"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"確認しました"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"完了するには [確認] をタップしてください"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"認証済み"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"指紋認証センサーをタップしてください"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋アイコン"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"顔を認証しています…"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 6a7460d..b3c7de7 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"გაუქმება"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"დადასტურება"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ხელახლა ცდა"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"დაფიქსირდა ცარიელი უბანი, შეეხეთ ამოცნობის გასაუქმებლად"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"შეეხეთ ავტორიზაციის გასაუქმებლად"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"გთხოვთ, ცადოთ ხელახლა"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"მიმდინარეობს თქვენი სახის ძებნა"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"სახის ამოცნობილია"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"დადასტურებული"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"დასასრულებლად შეეხეთ „დადასტურებას“"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ავტორიზებულია"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"შეეხეთ თითის ანაბეჭდის სენსორს"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"თითის ანაბეჭდის ხატულა"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"მიმდინარეობს თქვენი ძიება…"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 6e0fca7..e1fa4b2b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Бас тарту"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Растау"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Қайталап көріңіз"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Аймақта ештеңе көрсетілмеген. Оны басып, аутентификациядан бас тартыңыз."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Аутентификациядан бас тарту үшін түртіңіз."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Қайталап көріңіз."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Құрылғы бетіңізді талдап жатыр."</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Бет танылды."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Расталды"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Аяқтау үшін \"Растау\" түймесін түртіңіз."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аутентификацияланған"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Саусақ ізін оқу сканерін түртіңіз"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Саусақ ізі белгішесі"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Бет ізделуде…"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 16272fc..db93aa9 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"បោះ​បង់​"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"បញ្ជាក់"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ព្យាយាម​ម្ដង​ទៀត"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"កន្លែង​ទំនេរ សូមចុច​ដើម្បីបោះបង់​ការផ្ទៀងផ្ទាត់"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ចុចដើម្បីបោះបង់​ការផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"សូម​ព្យាយាម​ម្ដងទៀត"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"កំពុង​ផ្ទៀងផ្ទាត់​មុខរបស់អ្នក"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"បានផ្ទៀងផ្ទាត់​មុខ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"បានបញ្ជាក់"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ចុច \"បញ្ជាក់\" ដើម្បីបញ្ចប់"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"បាន​ផ្ទៀងផ្ទាត់"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ប៉ះ​ឧបករណ៍​ចាប់ស្នាម​ម្រាមដៃ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"រូបតំណាង​ស្នាម​ម្រាមដៃ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"កំពុងស្វែងរកអ្នក…"</string>
@@ -543,8 +544,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"វា​នឹង​នៅតែ​បង្ហាញ រហូតទាល់​តែអ្នក​ដកការដៅ។ អូសឡើងលើ​ឱ្យជាប់ ដើម្បី​ដក​ការដៅ។"</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"វា​នឹង​នៅតែ​បង្ហាញ រហូត​ទាល់​តែ​អ្នក​ដក​ការ​ដៅ។ សូម​សង្កត់​ប៊ូតុង​ទិដ្ឋភាពរួម​​ឲ្យ​ជាប់ ដើម្បី​ដក​ការ​ដៅ។"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"វា​នឹង​នៅតែ​បង្ហាញ រហូត​ទាល់​តែ​អ្នក​ដក​ការដៅ។ សូម​ចុច​ប៊ូតុង​ទំព័រដើម​ឱ្យ​ជាប់ ដើម្បី​ដក​ការ​ដៅ។"</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"ដើម្បី​ដក​ការ​ដៅ​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុង​ទិដ្ឋភាពរួម​ឱ្យ​ជាប់"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"ដើម្បី​ដក​ការ​ដៅ​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុងទំព័រដើម​ឱ្យ​ជាប់"</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"ដើម្បី​ដកខ្ទាស់​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុង​ទិដ្ឋភាពរួម​ឱ្យ​ជាប់"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"ដើម្បី​ដកខ្ទាស់​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុងទំព័រដើម​ឱ្យ​ជាប់"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"អូសឡើងលើ​ឱ្យជាប់ ដើម្បី​ដកការដៅ​អេក្រង់​នេះ"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"យល់​ហើយ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ទេ អរគុណ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79c4f2d..98589ad 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡಿ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ದೃಢೀಕರಿಸಿ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ಖಾಲಿ ಪ್ರದೇಶ, ದೃಢೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ದೃಢೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ನಿಮ್ಮ ಮುಖದ ದೃಢೀಕರಣಕ್ಕಾಗಿ ನಿರೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ಮುಖವನ್ನು ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ಪೂರ್ಣಗೊಳಿಸಲು ದೃಢೀಕರಿಸಿ ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್‌‌ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಐಕಾನ್"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ನಿಮಗಾಗಿ ಹುಡುಕಲಾಗುತ್ತಿದೆ…"</string>
@@ -450,7 +451,7 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"ಬಳಕೆದಾರರನ್ನು ತೆಗೆದುಹಾಕುವುದೇ?"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"ಈ ಬಳಕೆದಾರರ ಎಲ್ಲಾ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುವುದು."</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"ತೆಗೆದುಹಾಕಿ"</string>
-    <string name="battery_saver_notification_title" msgid="8614079794522291840">"ಬ್ಯಾಟರಿ ರಕ್ಷಕ ಆನ್ ಆಗಿದೆ"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಆನ್ ಆಗಿದೆ"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಹಿನ್ನೆಲೆ ಡೇಟಾವನ್ನು ಕಡಿಮೆ ಮಾಡುತ್ತದೆ"</string>
     <string name="battery_saver_notification_action_text" msgid="132118784269455533">"ಬ್ಯಾಟರಿ ಸೇವರ್‌ ಆಫ್ ಮಾಡಿ"</string>
     <string name="media_projection_dialog_text" msgid="8585357687598538511">"ರೆಕಾರ್ಡ್ ಮಾಡುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟಿಂಗ್ ಮಾಡುವಾಗ, ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯಂತಹ ಆಡಿಯೋ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು ಮತ್ತು ಸಂದೇಶಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಡಿಸ್‌ಪ್ಲೇ ಮಾಡಿದ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಿಂದ ಪ್ಲೇ ಮಾಡಿದ ಯಾವುದೇ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index b0a1671..03c277e 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -39,7 +39,7 @@
     <string name="battery_saver_start_action" msgid="8187820911065797519">"절전 모드 사용 설정"</string>
     <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"설정"</string>
     <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"Wi-Fi"</string>
-    <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"자동 화면 회전"</string>
+    <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"화면 자동 회전"</string>
     <string name="status_bar_settings_mute_label" msgid="554682549917429396">"무시"</string>
     <string name="status_bar_settings_auto_brightness_label" msgid="511453614962324674">"자동"</string>
     <string name="status_bar_settings_notifications" msgid="397146176280905137">"알림"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"취소"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"확인"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"다시 시도하세요."</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"비어 있는 공간, 탭하여 인증 취소"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"탭하여 인증 취소"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"다시 시도해 주세요."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"얼굴을 찾는 중"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"얼굴이 인증되었습니다."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"확인함"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"완료하려면 확인을 탭하세요."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"인증됨"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"지문 센서를 터치하세요."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"지문 아이콘"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"찾는 중..."</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"아래에 덜 급한 알림 표시"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"다시 탭하여 열기"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"위로 스와이프하여 열기"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"위로 스와이프하여 다시 시도해 주세요."</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"위로 스와이프하여 다시 시도해 주세요"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"조직에서 관리하는 기기입니다."</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>에서 관리하는 기기입니다."</string>
     <string name="phone_hint" msgid="4872890986869209950">"전화 기능을 사용하려면 아이콘에서 스와이프하세요."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index dd985c6..c82e5b0 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Жокко чыгаруу"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Ырастоо"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Кайталоо"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Бош жер калып калды, аутентификацияны жокко чыгаруу үчүн таптап коюңуз"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Аныктыгын текшерүүнү жокко чыгаруу үчүн таптаңыз"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Кайра аракет кылыңыз"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Жүзүңүз изделүүдө"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Жүздүн аныктыгы текшерилди"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Ырасталды"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Бүтүрүү үчүн \"Ырастоо\" баскычын басыңыз"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аныктыгы текшерилди"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Манжа изинин сенсорун басыңыз"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Манжа изинин сүрөтчөсү"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Жүзүңүз изделүүдө…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Анчейин шашылыш эмес эскертмелер төмөндө"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Ачуу үчүн кайра таптап коюңуз"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Ачуу үчүн өйдө сүрүңүз"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Кайра аракет кылуу үчүн экранды өйдө сүрүңүз"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Кайталоо үчүн экранды өйдө сүрүңүз"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Бул түзмөк уюмуңуз тарабынан башкарылат"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Бул түзмөк <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> тарабынан башкарылат"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Сүрөтчөнү серпип телефонго өтүңүз"</string>
@@ -457,7 +458,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"Жаздырып же тышкы экранга чыгаруу учурунда, бул функцияны аткарып жаткан колдонмо ойноткон аудиоңуз, сырсөздөрүңүз, төлөө маалыматыңыз, сүрөттөрүңүз жана билдирүүлөрүңүз сыяктуу экранда көрсөтүлгөн купуя маалыматты жаздырып калышы мүмкүн."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Тышкы экранга чыгарууда/жаздырууда купуя маалыматты ачыктоо"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Экинчи көрсөтүлбөсүн"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Бардыгын тазалап салуу"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Баарын тазалап салуу"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Башкаруу"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"Үнсүз билдирмелер"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Маанилүү эмес билдирмелердин баарын өчүрүү"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 90e78e8..5a8c5dc 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -36,4 +36,8 @@
 
     <dimen name="volume_tool_tip_right_margin">136dp</dimen>
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
+
+    <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
+         value in landscape since we have limited vertical space-->
+    <dimen name="bubble_padding_top">4dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index d99db74..4f4d291 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ຍົກເລີກ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ຢືນຢັນ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ລອງໃໝ່"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ພື້ນທີ່ຫວ່າງເປົ່າ, ແຕະເພື່ອຍົກເລີກການພິສູດຢືນຢັນ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ແຕະເພື່ອຍົກເລີກການກວດສອບຄວາມຖືກຕ້ອງ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ກະລຸນາລອງໃໝ່"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ກຳລັງເບິ່ງໃບໜ້າຂອງທ່ານ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ພິສູດຢືນຢັນໃບໜ້າແລ້ວ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ຢືນຢັນແລ້ວ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ແຕະຢືນຢັນເພື່ອສຳເລັດ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ແຕະໃສ່ເຊັນເຊີລາຍນິ້ວມື"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ໄອຄອນລາຍນິ້ວມື"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ກຳລັງຊອກຫາທ່ານ…"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 0355fc0..ca7c89e 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Atšaukti"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Patvirtinkite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Bandyti dar kartą"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tuščia sritis; palieskite, kad atšauktumėte autentifikavimą"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Palieskite, jei norite atšaukti autentifikavimą"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Bandykite dar kartą"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ieškoma veido"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Veidas autentifikuotas"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Patvirtinta"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Paliesk. „Patvirtinti“, kad užbaigtumėte"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikuota"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Palieskite piršto antspaudo jutiklį"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Piršto antspaudo piktograma"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ieškoma jūsų…"</string>
@@ -511,7 +512,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"„<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>“ naudoja „<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>“ įrenginiui tvarkyti."</string>
     <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administrat. gali stebėti ir tvark. nustat., įmonės prieigos par., progr., su įreng. susietus duomenis ir įreng. vietovės inform."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Sužinoti daugiau"</string>
+    <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Sužinokite daugiau"</string>
     <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Esate prisijungę prie programos „<xliff:g id="VPN_APP">%1$s</xliff:g>“, kuri gali stebėti tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"Atidaryti VPN nustatymus"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e6ac235..424e104 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Atcelt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Apstiprināt"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Mēģināt vēlreiz"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tukšs apgabals. Pieskarieties tam, lai atceltu autentificēšanu."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Pieskarieties, lai atceltu autentifikāciju."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Lūdzu, mēģiniet vēlreiz."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Tiek meklēta jūsu seja"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Seja autentificēta"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Apstiprināts"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Lai pabeigtu, pieskarieties Apstiprināt"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikācija veikta"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Pieskarieties pirksta nospieduma sensoram"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Pirksta nospieduma ikona"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Notiek jūsu sejas meklēšana…"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index a529dfe..dd86ede 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Откажи"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потврди"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Обиди се повторно"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празен регион, допрете за да ја откажете проверката"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Допрете за да ја откажете проверката"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Обидете се повторно"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Го бараме вашето лице"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицето е проверено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потврдено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Допрете „Потврди“ за да се заврши"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Проверена"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Допрете го сензорот за отпечатоци"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона за отпечатоци"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ве бараме вас…"</string>
@@ -192,7 +193,7 @@
     <string name="not_default_data_content_description" msgid="9194667237765917844">"Не е поставен да користи интернет"</string>
     <string name="cell_data_off" msgid="1051264981229902873">"Исклучи"</string>
     <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Се поврзува со Bluetooth."</string>
-    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Режим на работа во авион."</string>
+    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Авионски режим."</string>
     <string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN е вклучена."</string>
     <string name="accessibility_no_sims" msgid="3957997018324995781">"Нема SIM-картичка"</string>
     <string name="carrier_network_change_mode" msgid="8149202439957837762">"Променување на мрежата на операторот"</string>
@@ -347,7 +348,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Осветленост"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"Автоматски"</string>
     <string name="quick_settings_inversion_label" msgid="8790919884718619648">"Преврти ги боите"</string>
-    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Режим за корекција на боја"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Режим за корекција на бои"</string>
     <string name="quick_settings_more_settings" msgid="326112621462813682">"Повеќе поставки"</string>
     <string name="quick_settings_done" msgid="3402999958839153376">"Готово"</string>
     <string name="quick_settings_connected" msgid="1722253542984847487">"Поврзано"</string>
@@ -457,7 +458,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"При снимањето или емитувањето, услугата што ја обезбедува функцијава може да ги сними чувствителните информации што се прикажани на вашиот екран или пуштени од вашиот уред, вклучувајќи чувствителни информации како што се аудио, лозинки, информации за плаќање, фотографии и пораки."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Изложување чувствителни информации при емитување/снимање"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Не покажувај повторно"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Исчисти сè"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Управувајте"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"Тивки известувања"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Исчисти ги сите тивки известувања"</string>
@@ -597,7 +598,7 @@
     <string name="status_bar_ethernet" msgid="5044290963549500128">"Етернет"</string>
     <string name="status_bar_alarm" msgid="8536256753575881818">"Аларм"</string>
     <string name="status_bar_work" msgid="6022553324802866373">"Работен профил"</string>
-    <string name="status_bar_airplane" msgid="7057575501472249002">"Режим на работа во авион"</string>
+    <string name="status_bar_airplane" msgid="7057575501472249002">"Авионски режим"</string>
     <string name="add_tile" msgid="2995389510240786221">"Додај плочка"</string>
     <string name="broadcast_tile" msgid="3894036511763289383">"Емитувај плочка"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g> освен ако претходно не го исклучите ова"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 181eb4a..859e65b 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"റദ്ദാക്കുക"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"സ്ഥിരീകരിക്കുക"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"വീണ്ടും ശ്രമിക്കുക"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ശൂന്യമായ ഇടം, പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"നിങ്ങളുടെ മുഖത്തിന് വേണ്ടി തിരയുന്നു"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"മുഖം പരിശോധിച്ചുറപ്പിച്ചു"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"സ്ഥിരീകരിച്ചു"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"പൂർത്തിയാക്കാൻ സ്ഥിരീകരിക്കുക ടാപ്പ് ചെയ്യൂ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"പരിശോധിച്ചുറപ്പിച്ചു"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"വിരലടയാള സെൻസർ സ്‌പർശിക്കുക"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"വിരലടയാള ഐക്കൺ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"നിങ്ങൾക്കായി തിരയുന്നു…"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 085a0aeeb..3363af7 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Цуцлах"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Баталгаажуулах"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Дахин оролдох"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Нотолгоог цуцлахын тулд хоосон бүсийг товшино уу"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Нотолгоог цуцлахын тулд товшино уу"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Дахин оролдоно уу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Таны царайг хайж байна"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Царайг баталгаажууллаа"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Баталгаажсан"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Дуусгахын тулд баталгаажуулахыг товших"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Баталгаажуулагдсан"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Хурууны хээ мэдрэгчид хүрэх"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Хурууны хээний дүрс тэмдэг"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Таныг хайж байна…"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 4722ae9..f785fe9 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द करा"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"खात्री करा"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"पुन्हा प्रयत्न करा"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"प्रदेशाचे नाव रिक्त आहे, ऑथेंटिकेशन रद्द करण्यासाठी टॅप करा"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ऑथेंटिकेशन रद्द करण्यासाठी टॅप करा"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया पुन्हा प्रयत्न करा"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"तुमचा चेहरा शोधत आहे"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"चेहरा ऑथेंटिकेशन केलेला आहे"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"निश्चित केले"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"पूर्ण करण्यासाठी खात्री करा वर टॅप करा"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ऑथेंटिकेशन केलेले"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फिंगरप्रिंट सेन्सरला स्पर्श करा"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फिंगरप्रिंट आयकन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"तुमच्यासाठी शोधत आहे…"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 1bc7ca9..352c376 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Batal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Sahkan"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Cuba lagi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Kawasan kosong. Ketik untuk membatalkan pengesahan"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ketik untuk membatalkan pengesahan"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Sila cuba lagi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Mencari wajah anda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Wajah disahkan"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Disahkan"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ketik Sahkan untuk menyelesaikan"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Disahkan"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sentuh penderia cap jari"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon cap jari"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Mencari anda…"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 51aaaa2..d136042 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"မလုပ်တော့"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"အတည်ပြုပါ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ထပ်စမ်းကြည့်ရန်"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"နေရာလွတ်၊ အထောက်အထားစိစစ်ခြင်းကို မလုပ်တော့ရန် တို့ပါ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်ရန် တို့ပါ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ထပ်စမ်းကြည့်ပါ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"သင့်မျက်နှာကို ရှာနေသည်"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"မျက်နှာ အထောက်အထားစိစစ်ပြီးပြီ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"အတည်ပြုပြီးပြီ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"အပြီးသတ်ရန်အတွက် \'အတည်ပြုရန်\' ကို တို့ပါ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"အထောက်အထားစိစစ်ပြီးပြီ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"လက်ဗွေအာရုံခံကိရိယာကို တို့ပါ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"လက်ဗွေ သင်္ကေတ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"သင့်ကို ရှာဖွေနေသည်…"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index ce611e0..86f0b38 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Avbryt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekreft"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Prøv på nytt"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tomt område – trykk for å avbryte autentisering"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Trykk for å avbryte autentiseringen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Prøv igjen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ser etter ansiktet ditt"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansiktet er autentisert"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekreftet"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Trykk på Bekreft for å fullføre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentisert"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Trykk på fingeravtrykkssensoren"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon for fingeravtrykk"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ser etter deg …"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 6ef85b5..4c17904 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द गर्नुहोस्"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"पुष्टि गर्नुहोस्"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"फेरि प्रयास गर्नुहोस्"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"खाली क्षेत्र, प्रमाणीकरण रद्द गर्न ट्याप गर्नुहोस्"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"प्रमाणीकरण रद्द गर्न ट्याप गर्नुहोस्"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया फेरि प्रयास गर्नुहोस्"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"तपाईंको अनुहार खोज्दै"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"अनुहार प्रमाणीकरण गरियो"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"पुष्टि भयो"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"पूरा गर्नका लागि पुष्टि गर्नुहोस् नामक विकल्पमा ट्याप गर्नुहोस्"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"प्रमाणीकरण गरियो"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्‌"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फिंगरप्रिन्ट जनाउने आइकन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"तपाईंलाई खोज्दै…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न परिदृश्य बटनलाई छोइराख्नुहोस्।"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न गृह नामक बटनलाई छोइराख्नुहोस्।"</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"यस स्क्रिनलाई अनपनि गर्न पछाडि र परिदृश्य नामक बटनहरूलाई छोइराख्नुहोस्"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"यस स्क्रिनलाई अनपनि गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"यस स्क्रिनलाई अनपिन गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"यो स्क्रिन अनपिन गर्न माथितिर स्वाइप गरी थिचिराख्नुहोस्"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 7b55d18..0febc8e 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -83,4 +83,9 @@
     <color name="biometric_dialog_accent">#ff80cbc4</color> <!-- light teal -->
     <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
 
+    <color name="GM2_green_500">#FF41Af6A</color>
+    <color name="GM2_blue_500">#5195EA</color>
+    <color name="GM2_red_500">#E25142</color>
+    <color name="GM2_yellow_500">#F5C518</color>
+
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 305b46a..c62c579 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuleren"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bevestigen"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Opnieuw proberen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Leeg gebied. Tik om de verificatie te annuleren."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tik om de verificatie te annuleren"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probeer het opnieuw"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Er wordt naar je gezicht gezocht"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gezicht geverifieerd"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bevestigd"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tik op Bevestigen om te voltooien"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Geverifieerd"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Raak de vingerafdruksensor aan"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Vingerafdrukpictogram"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Jouw gezicht zoeken…"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index e9b150c..d295925 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ନିଶ୍ଚିତ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ଖାଲି ଅଞ୍ଚଳ, ପ୍ରାମାଣିକତା ବାତିଲ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ପ୍ରାମାଣିକତା ବାତିଲ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ଆପଣଙ୍କର ମୁହଁକୁ ପ୍ରମାଣ କରୁଛି"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ମୁହଁ ପ୍ରାମାଣିକତା ହୋଇଛି"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ସୁନିଶ୍ଚିତ କରାଯାଇଛି"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ସୁନିଶ୍ଚିତ କରନ୍ତୁରେ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ପ୍ରାମାଣିକତା ହୋଇଛି"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ଟିପଚିହ୍ନ ସେନସର୍‌କୁ ଛୁଅଁନ୍ତୁ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ଆଙ୍ଗୁଠି ଚିହ୍ନ ଆଇକନ୍"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ଆପଣଙ୍କୁ ଚିହ୍ନଟ କରୁଛି…"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 742080c..b75deb9 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ਰੱਦ ਕਰੋ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ਪੁਸ਼ਟੀ ਕਰੋ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ਖਾਲੀ ਖੇਤਰ, ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ਤੁਹਾਡਾ ਚਿਹਰਾ ਲੱਭਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ਚਿਹਰਾ ਪ੍ਰਮਾਣੀਕਿਰਤ ਹੋਇਆ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ਪੁਸ਼ਟੀ ਕੀਤੀ ਗਈ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ਪੂਰਾ ਕਰਨ ਲਈ ਪੁਸ਼ਟੀ ਕਰੋ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ਪ੍ਰਮਾਣਿਤ ਹੋਇਆ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਪੱਰਸ਼ ਕਰੋ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਦਾ ਪ੍ਰਤੀਕ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ਤੁਹਾਡੀ ਪਛਾਣ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"ਹੇਠਾਂ ਘੱਟ ਲਾਜ਼ਮੀ ਸੂਚਨਾਵਾਂ"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"ਖੋਲ੍ਹਣ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"ਖੋਲ੍ਹਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਉੱਤੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਤੁਹਾਡੇ ਸੰਗਠਨ ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"ਇਹ ਡੀਵਾਈਸ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
     <string name="phone_hint" msgid="4872890986869209950">"ਫ਼ੋਨ ਲਈ ਪ੍ਰਤੀਕ ਤੋਂ ਸਵਾਈਪ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 93a2c12..d372b3d 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anuluj"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potwierdź"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Spróbuj jeszcze raz"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Pusty obszar, kliknij, by anulować uwierzytelnianie"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Kliknij, by anulować uwierzytelnianie"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Spróbuj ponownie"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Szukam Twojej twarzy"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Twarz rozpoznana"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potwierdzono"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Aby zakończyć, kliknij Potwierdź"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Uwierzytelniono"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotknij czytnika linii papilarnych"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona odcisku palca"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Szukam Cię…"</string>
@@ -465,10 +466,10 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"Podczas nagrywania lub przesyłania usługa udostępniająca tę funkcję może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu takie jak dźwięki czy podawane hasła, informacje o płatnościach, zdjęcia i wiadomości."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Ujawnianie poufnych informacji podczas przesyłania/nagrywania"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Nie pokazuj ponownie"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Ukryj wszystkie"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Usuń wszystkie"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Zarządzaj"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Powiadomienia ciche"</string>
-    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Wyczyść wszystkie ciche powiadomienia"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Ciche powiadomienia"</string>
+    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Usuń wszystkie ciche powiadomienia"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"Powiadomienia wstrzymane przez tryb Nie przeszkadzać"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"Rozpocznij teraz"</string>
     <string name="empty_shade_text" msgid="708135716272867002">"Brak powiadomień"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 7a57049..2b76157 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque nela para cancelar a autenticação"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Procurando seu rosto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em \"Confirmar\" para concluir"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressão digital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Procurando você…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Para liberar esta tela, mantenha os botões Voltar e Visão geral pressionados"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar essa tela, toque nos botões Voltar e Início e mantenha-os pressionados"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar esta tela, mantenha os botões Voltar e Início pressionados"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para liberar esta tela, deslize para cima e pressione"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index fb45ea7..17cc86f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque para cancelar a autenticação."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"A procurar o seu rosto…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmado"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em Confirmar para concluir."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressões digitais."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"À sua procura…"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 7a57049..2b76157 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque nela para cancelar a autenticação"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Procurando seu rosto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em \"Confirmar\" para concluir"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressão digital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Procurando você…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Para liberar esta tela, mantenha os botões Voltar e Visão geral pressionados"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar essa tela, toque nos botões Voltar e Início e mantenha-os pressionados"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar esta tela, mantenha os botões Voltar e Início pressionados"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para liberar esta tela, deslize para cima e pressione"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e897f16..db8f87d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anulați"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmați"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Încercați din nou"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zonă goală, atingeți pentru a anula autentificarea"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Atingeți pentru a anula autentificarea"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Încercați din nou"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Se caută chipul"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Chip autentificat"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Atingeți Confirmați pentru a finaliza"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentificat"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Atingeți senzorul de amprente"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Pictograma amprentă"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Vă căutăm…"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index c68f81e..f7527d9 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Отмена"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Подтвердить"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Повторить попытку"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Пустая область. Нажмите на нее, чтобы отменить аутентификацию."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Нажмите, чтобы отменить аутентификацию"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Повторите попытку"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Распознавание лица"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицо распознано"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Подтверждено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Нажмите \"Подтвердить\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аутентификация выполнена"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Прикоснитесь к сканеру отпечатков пальцев."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок отпечатка пальца"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Поиск лица…"</string>
@@ -405,7 +406,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Показать менее важные уведомления"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Нажмите ещё раз, чтобы открыть"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Проведите вверх, чтобы открыть"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Чтобы повторить попытку, проведите по экрану вверх."</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Чтобы повторить попытку, проведите вверх"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Этим устройством управляет ваша организация"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Этим устройством управляет компания \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string>
     <string name="phone_hint" msgid="4872890986869209950">"Телефон: проведите от значка"</string>
@@ -549,8 +550,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"Экран будет зафиксирован, пока вы не отмените блокировку (для этого нужно провести вверх и удерживать)."</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Обзор\"."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Главный экран\"."</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"Чтобы отменить блокировку, нажмите и удерживайте кнопки \"Назад\" и \"Обзор\""</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Чтобы отменить блокировку, нажмите и удерживайте кнопки \"Назад\" и \"Главный экран\""</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"Чтобы открепить экран, нажмите и удерживайте кнопки \"Назад\" и \"Обзор\"."</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Чтобы открепить экран, нажмите и удерживайте кнопки \"Назад\" и \"Главный экран\"."</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Чтобы открепить этот экран, проведите по нему вверх и задержите руку в крайнем положении."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ОК"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Нет, спасибо"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 2c54218..c87ef5a 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"අවලංගු කරන්න"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"තහවුරු කරන්න"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"නැවත උත්සාහ කරන්න"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"හිස් කලාපය, සත්‍යාපනය අවලංගු කිරීමට තට්ටු කරන්න"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"සත්‍යාපනය අවලංගු කිරීමට තට්ටු කරන්න"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"නැවත උත්සාහ කරන්න"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ඔබේ මුහුණ සොයනු ලැබේ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"මුහුණ සත්‍යාපන කළා"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"තහවුරු කළා"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"සම්පූර්ණ කිරීමට තහවුරු කරන්න තට්ටු කර."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"සත්‍යාපනය විය"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ඇඟිලි සලකුණු සංවේදකය ස්පර්ශ කරන්න"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ඇඟිලි සලකුණු නිරූපකය"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ඔබව සොයමින්…"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index d14f928..1e171a1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Zrušiť"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdiť"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Skúsiť znova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prázdna oblasť, klepnutím zrušte overenie"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Klepnutím zrušíte overenie"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Skúste to znova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Hľadá sa vaša tvár"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Tvár bola overená"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrdené"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Overenie dokončíte klepnutím na Potvrdiť"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Overené"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotknite sa senzora odtlačkov prstov"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona odtlačku prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hľadáme vás…"</string>
@@ -405,7 +406,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Menej naliehavé upozornenia sa nachádzajú nižšie"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Upozornenie otvoríte opätovným klepnutím"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Otvorte potiahnutím prstom nahor"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Potiahnutím nahor to skúsite znova"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Potiahnutím nahor to skúste znova"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Toto zariadenie spravuje vaša organizácia"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Toto zariadenie spravuje organizácia <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Telefón otvoríte prejdením prstom od ikony"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 63358ba..b342ce6 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Prekliči"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potrdite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Poskusi znova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazno območje. Dotaknite se, da prekličete preverjanje pristnosti."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Če želite preklicati preverjanje pristnosti, se dotaknite"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Poskusite znova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Iskanje obraza"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Pristnost obraza je potrjena"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potrjeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Za dokončanje se dotaknite »Potrdite«"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Preverjena pristnost"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotaknite se tipala prstnih odtisov"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona prstnih odtisov"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Preverjanje vašega obraza …"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index bd026dc..4fba770 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -96,7 +96,7 @@
     <string name="usb_preference_title" msgid="6551050377388882787">"Opsionet e transferimit të dosjeve të USB-së"</string>
     <string name="use_mtp_button_title" msgid="4333504413563023626">"Lidh si një lexues \"media\" (MTP)"</string>
     <string name="use_ptp_button_title" msgid="7517127540301625751">"Montoje si kamerë (PTP)"</string>
-    <string name="installer_cd_button_title" msgid="2312667578562201583">"Instalo apl. \"Transferimi i skedarëve\" për \"Mac\""</string>
+    <string name="installer_cd_button_title" msgid="2312667578562201583">"Instalo \"Transferimi i skedarëve të Android\" për Mac"</string>
     <string name="accessibility_back" msgid="567011538994429120">"Prapa"</string>
     <string name="accessibility_home" msgid="8217216074895377641">"Faqja bazë"</string>
     <string name="accessibility_menu" msgid="316839303324695949">"Menyja"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anulo"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Konfirmo"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Provo përsëri"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Rajon bosh, trokit për të anuluar vërtetimin"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Trokit për të anuluar vërtetimin"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Provo përsëri"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Po kërkon për fytyrën tënde"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Fytyra u vërtetua"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Konfirmuar"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Trokit \"Konfirmo\" për ta përfunduar"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"U vërtetua"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Prek sensorin e gjurmës së gishtit"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona e gjurmës së gishtit"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Po të kërkojmë…"</string>
@@ -298,7 +299,7 @@
     <string name="dessert_case" msgid="1295161776223959221">"\"Kutia e ëmbëlsirës\""</string>
     <string name="start_dreams" msgid="5640361424498338327">"Mbrojtësi i ekranit"</string>
     <string name="ethernet_label" msgid="7967563676324087464">"Eternet"</string>
-    <string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"Trokit dhe mbaj prekur ikonat për më shumë opsione"</string>
+    <string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"Trokit dhe mbaj të shtypur ikonat për më shumë opsione"</string>
     <string name="quick_settings_dnd_label" msgid="7112342227663678739">"Mos shqetëso"</string>
     <string name="quick_settings_dnd_priority_label" msgid="483232950670692036">"Vetëm me prioritet"</string>
     <string name="quick_settings_dnd_alarms_label" msgid="2559229444312445858">"Vetëm alarmet"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 4d289dc..310fc6f 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Откажи"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потврди"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Пробај поново"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празна област, додирните да бисте отказали потврду идентитета"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Додирните да бисте отказали потврду идентитета"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Пробајте поново"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Тражи се ваше лице"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лице је потврђено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потврђено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Додирните Потврди да бисте завршили"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Идентитет је потврђен"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Додирните сензор за отисак прста"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона отиска прста"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Тражимо вас…"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index b891b93..54769cd 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Avbryt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekräfta"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Försök igen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Området är tomt. Tryck för att avbryta autentiseringen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tryck för att avbryta autentiseringen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Försök igen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Söker efter ditt ansikte"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansiktet har autentiserats"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekräftat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Slutför genom att trycka på Bekräfta"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentiserad"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Tryck på fingeravtryckssensorn"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon för fingeravtryck"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Håller utkik efter dig …"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b37405a..3f9278a 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ghairi"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Thibitisha"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Jaribu tena"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Eneo lisilo na chochote, gusa ili ughairi uthibitishaji"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Gusa ili ughairi uthibitishaji"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tafadhali jaribu tena"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Inatafuta uso wako"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Uso umethibitishwa"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Imethibitishwa"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Gusa Thibitisha ili ukamilishe"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Umethibitishwa"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Gusa kitambua alama ya kidole"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Aikoni ya alama ya kidole"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Inakutafuta…"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index d56ccef..0f36889 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ரத்துசெய்"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"உறுதிப்படுத்துக"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"மீண்டும் முயல்க"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"காலியான பகுதி, அங்கீகாரத்தை ரத்துசெய்யத் தட்டவும்"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"பயோமெட்ரிக் அடையாளத்தை ரத்துசெய்ய தட்டவும்"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"மீண்டும் முயலவும்"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"உங்கள் முகத்தை அங்கீகரிக்கிறது"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"முகம் அங்கீகரிக்கப்பட்டது"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"உறுதிப்படுத்தப்பட்டது"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"முடிக்க \'உறுதிப்படுத்து\' என்பதை தட்டவும்"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"அங்கீகரிக்கப்பட்டது"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"கைரேகை சென்சாரைத் தொடவும்"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"கைரேகை ஐகான்"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"உங்கள் முகத்தைத் தேடுகிறது…"</string>
@@ -506,18 +507,18 @@
     <string name="monitoring_description_do_body" msgid="3639594537660975895">"உங்கள் நிர்வாகியால் அமைப்புகள், நிறுவன அணுகல், ஆப்ஸ், சாதனத்துடன் தொடர்புடைய டேட்டா, சாதன இருப்பிடத் தகவல் ஆகியவற்றைக் கண்காணிக்கவும் நிர்வகிக்கவும் முடியும்."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
     <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"மேலும் அறிக"</string>
-    <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"VPN அமைப்புகளைத் திற"</string>
     <string name="monitoring_description_ca_cert_settings_separator" msgid="4987350385906393626">" "</string>
     <string name="monitoring_description_ca_cert_settings" msgid="5489969458872997092">"நம்பகமான அனுமதிச் சான்றுகளைத் திற"</string>
     <string name="monitoring_description_network_logging" msgid="7223505523384076027">"உங்கள் நிர்வாகி நெட்வொர்க் பதிவெடுத்தலை இயக்கியுள்ளார், இது சாதனத்தில் ட்ராஃபிக்கைக் கண்காணிக்கும்.\n\nமேலும் தகவலுக்கு, உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
-    <string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN இணைப்பை அமைக்க, பயன்பாட்டிற்கு அனுமதி வழங்கியுள்ளீர்கள்.\n\nஇந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட, உங்கள் சாதனத்தையும் நெட்வொர்க் செயல்பாட்டையும் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN இணைப்பை அமைக்க, பயன்பாட்டிற்கு அனுமதி வழங்கியுள்ளீர்கள்.\n\nஇந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட, உங்கள் சாதனத்தையும் நெட்வொர்க் செயல்பாட்டையும் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது.\n\nஉங்கள் நிர்வாகியால் பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்.\n\nமேலும் தகவலுக்கு, உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்.\n\nஉங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய VPN உடனும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
     <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
     <string name="monitoring_description_app" msgid="1828472472674709532">"மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள்."</string>
-    <string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
-    <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_app_work" msgid="4612997849787922906">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nமேலும் தகவலுக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
     <string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nஉங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> உடனும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
     <string name="keyguard_indication_trust_unlocked" msgid="2712865815371519117">"TrustAgent இதைத் திறந்தே வைத்துள்ளது"</string>
@@ -845,7 +846,7 @@
     <string name="pip_skip_to_prev" msgid="1955311326688637914">"முந்தையதற்குச் செல்"</string>
     <string name="thermal_shutdown_title" msgid="4458304833443861111">"வெப்பத்தினால் ஃபோன் ஆஃப் செய்யப்பட்டது"</string>
     <string name="thermal_shutdown_message" msgid="9006456746902370523">"இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது"</string>
-    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n	• அதிகளவு தரவைப் பயன்படுத்தும் பயன்பாடுகளை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n	• பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n	• அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
+    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n	• அதிகளவு தரவைப் பயன்படுத்தும் ஆப்ஸை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n	• பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n	• அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
     <string name="high_temp_title" msgid="4589508026407318374">"மொபைல் சூடாகிறது"</string>
     <string name="high_temp_notif_message" msgid="5642466103153429279">"மொபைலின் வெப்ப அளவு குறையும் போது, சில அம்சங்களைப் பயன்படுத்த முடியாது"</string>
     <string name="high_temp_dialog_message" msgid="6840700639374113553">"உங்கள் மொபைலின் வெப்ப அளவு தானாகவே குறையும். தொடர்ந்து நீங்கள் மொபைலைப் பயன்படுத்தலாம், ஆனால் அதன் வேகம் குறைவாக இருக்கக்கூடும்.\n\nமொபைலின் வெப்ப அளவு குறைந்தவுடன், அது இயல்பு நிலையில் இயங்கும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 350ccc6..62fe95b 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"రద్దు చేయి"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"నిర్ధారించు"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"మళ్లీ ప్రయత్నించు"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ఖాళీ ప్రదేశం, ప్రామాణీకరణను రద్దు చేయడానికి నొక్కండి"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ప్రామాణీకరణను రద్దు చేయడానికి నొక్కండి"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"దయచేసి మళ్ళీ ప్రయత్నించండి"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"మీ ముఖాన్ని క్యాప్చర్ చేస్తోంది"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ముఖం ప్రామాణీకరించబడింది"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"నిర్ధారించబడింది"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"పూర్తి చేయడానికి \"నిర్ధారించు\" నొక్కండి"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ప్రామాణీకరించబడింది"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"వేలిముద్ర సెన్సార్‌ను తాకండి"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"వేలిముద్ర చిహ్నం"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"మీ కోసం చూస్తోంది…"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 9d39859..eb6810a0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ยกเลิก"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ยืนยัน"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ลองอีกครั้ง"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"พื้นที่ว่าง แตะเพื่อยกเลิกการตรวจสอบสิทธิ์"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"แตะเพื่อยกเลิกการตรวจสอบสิทธิ์"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"โปรดลองอีกครั้ง"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"กำลังมองหาใบหน้าของคุณ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ตรวจสอบสิทธิ์ใบหน้าแล้ว"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ยืนยันแล้ว"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"แตะยืนยันเพื่อดำเนินการให้เสร็จสมบูรณ์"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ตรวจสอบสิทธิ์แล้ว"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"แตะเซ็นเซอร์ลายนิ้วมือ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ไอคอนลายนิ้วมือ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"กำลังหาใบหน้าคุณ…"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 56da232..f6abdcb 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Kanselahin"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Kumpirmahin"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Subukang muli"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Walang laman na rehiyon, mag-tap para kanselahin ang pag-authenticate"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"I-tap para kanselahin ang pag-authenticate"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pakisubukan ulit"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Hinahanap ang iyong mukha"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Na-authenticate ang mukha"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Nakumpirma"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"I-tap ang Kumpirmahin para kumpletuhin"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Na-authenticate"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Pindutin ang fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icon ng fingerprint"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hinahanap ka…"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 257b9de..b5e1702 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"İptal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Onaylayın"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tekrar dene"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Boş alan, yetkilendirmeyi iptal etmek için dokunun"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Kimlik doğrulama işlemini iptal etmek için dokunun"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Lütfen tekrar deneyin"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Yüzünüz aranıyor"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Yüz kimliği doğrulandı"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Onaylandı"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tamamlamak için Onayla\'ya dokunun"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Kimliği Doğrulandı"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Parmak izi sensörüne dokunun"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Parmak izi simgesi"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Yüzünüz tanınmaya çalışılıyor…"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index d82acf7..36bec1e 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Скасувати"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Підтвердити"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Повторити спробу"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Порожнє місце, торкніться, щоб скасувати автентифікацію"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Натисніть, щоб скасувати автентифікацію"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Повторіть спробу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Триває розпізнавання обличчя"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Обличчя автентифіковано"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Підтверджено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Щоб завершити, натисніть \"Підтвердити\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Автентифіковано"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Торкніться сканера відбитків пальців"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок відбитка пальця"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Пошук обличчя…"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 7d4f177..5fceabe 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"منسوخ کریں"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تصدیق کریں"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"دوبارہ کوشش کریں"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"خالی جگہیں، تصدیق کو منسوخ کرنے کے لیے تھپتھپائیں"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"تصدیق کو منسوخ کرنے کے لیے تھپتھپائیں"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"براہ کرم دوبارہ کوشش کریں"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"آپ کا چہرہ تلاش کیا جا رہا ہے"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"چہرے کی تصدیق ہو گئی"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تصدیق شدہ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"مکمل کرنے کیلئے \'تصدیق کریں\' تھپتھپائیں"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"تصدیق کردہ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"فنگر پرنٹ سینسر پر ٹچ کریں"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"فنگر پرنٹ آئیکن"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"آپ کے لیے تلاش کیا جا رہا ہے…"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e4a4548..37651a5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -60,7 +60,7 @@
     <string name="usb_debugging_always" msgid="303335496705863070">"Doimo ushbu kompyuterdan ruxsat berilsin"</string>
     <string name="usb_debugging_allow" msgid="2272145052073254852">"Ruxsat berish"</string>
     <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB orqali nosozliklarni tuzatishga ruxsat berilmagan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni tuzatish funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga o‘ting."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni aniqlash funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga o‘ting."</string>
     <string name="usb_contaminant_title" msgid="206854874263058490">"USB port faolsizlashtirildi"</string>
     <string name="usb_contaminant_message" msgid="7379089091591609111">"Qurilmangizni suyuqlik va turli parchalardan himoya qilish uchun USB port faolsizlashtiriladi va hech qanday aksessuarni aniqlay olmaydi.\n\nUSB portdan xavfsiz foydalanish mumkin boʻlganda, sizga xabar beriladi."</string>
     <string name="usb_port_enabled" msgid="7906141351687694867">"Quvvatlash moslamalari va aksessuarlarni aniqlash uchun USB port yoqildi"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Bekor qilish"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"OK"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Qayta urinish"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Boʻsh hudud, tekshiruvni bekor qilish uchun bosing"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tekshiruvni bekor qilish uchun bosing"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Qayta urining"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Yuz tekshirilmoqda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Yuzingiz aniqlandi"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Tasdiqlangan"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tasdiqlash uchun tegining"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Tasdiqlandi"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Barmoq izi skaneriga tegining"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Barmoq izi belgisi"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Yuzingiz tekshirilmoqda…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Kam ahamiyatli bildirishnomalarni pastda ko‘rsatish"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Ochish uchun yana bosing"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Ochish uchun tepaga suring"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Tepaga suring va qayta urining"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Qayta urinish uchun tepaga suring"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Bu – tashkilotingiz tomonidan boshqariladigan qurilma"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Bu – <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> tomonidan boshqariladigan qurilma"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Telefonni ochish uchun suring"</string>
@@ -459,7 +460,7 @@
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Boshqa ko‘rsatilmasin"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"Hammasini tozalash"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Boshqarish"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Ovozsiz bildirishnomalar"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Sokin bildirishnomalar"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Barcha tovushsiz bildirishnomalarni tozalash"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilingan"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"Boshlash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d3caa2b..f678265 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Hủy"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Xác nhận"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Thử lại"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Vùng trống, nhấn để hủy quá trình xác thực"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Nhấn để hủy quá trình xác thực"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vui lòng thử lại"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Đang tìm khuôn mặt của bạn"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Đã xác thực khuôn mặt"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Ðã xác nhận"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Nhấn vào Xác nhận để hoàn tất"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Đã xác thực"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Chạm vào cảm biến vân tay"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Biểu tượng vân tay"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Đang tìm kiếm bạn…"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index ca72b08..05509f6 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"确认"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"重试"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白区域,点按即可取消身份验证"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"点按即可取消身份验证"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"请重试"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在查找您的面孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"面孔身份验证成功"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"已确认"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"点按“确认”即可完成"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"已经过身份验证"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"请触摸指纹传感器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指纹图标"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在查找您的面孔…"</string>
@@ -376,7 +377,7 @@
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4453017157391574402">"在日出时关闭"</string>
     <string name="quick_settings_night_secondary_label_on_at" msgid="6256314040368487637">"在<xliff:g id="TIME">%s</xliff:g> 开启"</string>
     <string name="quick_settings_secondary_label_until" msgid="2749196569462600150">"直到<xliff:g id="TIME">%s</xliff:g>"</string>
-    <string name="quick_settings_ui_mode_night_label" msgid="3419947801072692538">"深色主题背景"</string>
+    <string name="quick_settings_ui_mode_night_label" msgid="3419947801072692538">"深色主题"</string>
     <string name="quick_settings_ui_mode_night_label_battery_saver" msgid="7438725724589758362">"深色主题背景\n省电模式"</string>
     <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC 已停用"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 128e1ca..b57e61f 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"請再試一次"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白區域,輕按即可取消驗證"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"輕按即可取消驗證"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"請再試一次"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在尋找您的臉孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"臉孔已經驗證"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"已確認"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"輕按 [確定] 以完成"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"驗證咗"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"請輕觸指紋感應器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋圖示"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在搜尋您的臉孔…"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6bacf70..14fedbb 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"再試一次"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白的區域,輕觸即可取消驗證"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"輕觸即可取消驗證"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"請再試一次"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在尋找你的臉孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"臉孔驗證成功"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"確認完畢"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"輕觸 [確認] 完成驗證設定"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"已通過驗證"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"請輕觸指紋感應器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋圖示"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在尋找你的臉孔…"</string>
@@ -459,8 +460,8 @@
     <string name="media_projection_remember_text" msgid="3103510882172746752">"不要再顯示"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"全部清除"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"管理"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"無聲通知"</string>
-    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有無聲通知"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"靜音通知"</string>
+    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有靜音通知"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"「零打擾」模式已將通知設為暫停"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"立即開始"</string>
     <string name="empty_shade_text" msgid="708135716272867002">"沒有通知"</string>
@@ -645,7 +646,7 @@
     <string name="inline_block_button" msgid="8735843688021655065">"封鎖"</string>
     <string name="inline_keep_button" msgid="6665940297019018232">"繼續顯示"</string>
     <string name="inline_minimize_button" msgid="966233327974702195">"最小化"</string>
-    <string name="inline_silent_button_silent" msgid="5315879183296940969">"無聲"</string>
+    <string name="inline_silent_button_silent" msgid="5315879183296940969">"靜音"</string>
     <string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"繼續顯示通知但不發出音效"</string>
     <string name="inline_silent_button_alert" msgid="6008435419895088034">"快訊"</string>
     <string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"繼續顯示通知"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 2306e4a..c158c77 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Khansela"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Qinisekisa"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Zama futhi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Isifunda esingenalutho, thepha ukuze ukhansele ukufakazela ubuqiniso"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Thepha ukuze ukhansele ukufakazela ubuqiniso"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Sicela uzame futhi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ifuna ubuso bakho"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ubuso bufakazelwe ubuqiniso"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Kuqinisekisiwe"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Thepha okuthi Qinisekisa ukuze uqedele"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Kugunyaziwe"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Thinta inzwa yesigxivizo somunwe"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Isithonjana sezigxivizo zeminwe"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Kufunwa wena…"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 61816f6..bda1c52 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -204,4 +204,6 @@
 
     <color name="GM2_yellow_500">#FFFBBC04</color>
     <color name="GM2_green_500">#FF34A853</color>
+    <color name="GM2_blue_500">#FF4285F4</color>
+
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6e8e823..61210d3 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -273,7 +273,6 @@
 
     <!-- SystemUI Services: The classes of the stuff to start. -->
     <string-array name="config_systemUIServiceComponents" translatable="false">
-        <item>com.android.systemui.Dependency$DependencyCreator</item>
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
@@ -292,7 +291,7 @@
         <item>com.android.systemui.LatencyTester</item>
         <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
         <item>com.android.systemui.ScreenDecorations</item>
-        <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
+        <item>com.android.systemui.biometrics.AuthController</item>
         <item>com.android.systemui.SliceBroadcastRelayHandler</item>
         <item>com.android.systemui.SizeCompatModeActivityController</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
@@ -304,7 +303,6 @@
 
     <!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents -->
     <string-array name="config_systemUIServiceComponentsPerUser" translatable="false">
-        <item>com.android.systemui.Dependency$DependencyCreator</item>
         <item>com.android.systemui.util.NotificationChannels</item>
     </string-array>
 
@@ -482,4 +480,7 @@
     <!-- Preferred refresh rate at keyguard, if supported by the display -->
     <integer name="config_keyguardRefreshRate">-1</integer>
 
+    <!-- Whether or not to add a "people" notifications section -->
+    <bool name="config_usePeopleFiltering">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index be815e1..1079206 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -457,6 +457,7 @@
     <dimen name="qs_page_indicator_width">16dp</dimen>
     <dimen name="qs_page_indicator_height">8dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
+    <dimen name="qs_tile_detail_padding">3dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
     <dimen name="qs_tile_divider_height">1dp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
@@ -1010,6 +1011,8 @@
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
     <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
     <dimen name="biometric_dialog_border_padding">4dp</dimen>
+    <dimen name="biometric_dialog_elevation">1dp</dimen>
+    <dimen name="biometric_dialog_icon_padding">16dp</dimen>
 
     <!-- Wireless Charging Animation values -->
     <dimen name="wireless_charging_dots_radius_start">0dp</dimen>
@@ -1102,18 +1105,23 @@
     <dimen name="bubble_flyout_pointer_size">6dp</dimen>
     <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
     <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
-    <!-- Padding around a collapsed bubble -->
-    <dimen name="bubble_view_padding">0dp</dimen>
-    <!-- Padding between bubbles when displayed in expanded state -->
-    <dimen name="bubble_padding">8dp</dimen>
+    <!-- Padding between status bar and bubbles when displayed in expanded state -->
+    <dimen name="bubble_padding_top">16dp</dimen>
     <!-- Size of individual bubbles. -->
-    <dimen name="individual_bubble_size">52dp</dimen>
+    <dimen name="individual_bubble_size">60dp</dimen>
+    <!-- Size of bubble icon bitmap. -->
+    <dimen name="bubble_icon_bitmap_size">52dp</dimen>
+    <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
+    <dimen name="bubble_touch_padding">12dp</dimen>
     <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
-    <dimen name="bubble_dismiss_encircle_size">56dp</dimen>
+    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
     <!-- How much to inset the icon in the circle -->
     <dimen name="bubble_icon_inset">16dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
     <dimen name="bubble_expanded_view_padding">4dp</dimen>
+    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+         a slight touch slop around the expanded view. -->
+    <dimen name="bubble_expanded_view_slop">8dp</dimen>
     <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">180dp</dimen>
     <!-- Height of the triangle that points to the expanded bubble -->
@@ -1122,10 +1130,8 @@
     <dimen name="bubble_pointer_width">6dp</dimen>
     <!-- Extra padding around the dismiss target for bubbles -->
     <dimen name="bubble_dismiss_slop">16dp</dimen>
-    <!-- Height of the header within the expanded view. -->
-    <dimen name="bubble_expanded_header_height">48dp</dimen>
-    <!-- Left and right padding applied to the header. -->
-    <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+    <!-- Height of button allowing users to adjust settings for bubbles. -->
+    <dimen name="bubble_settings_size">48dp</dimen>
     <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
     <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
     <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
@@ -1138,12 +1144,10 @@
     <dimen name="bubble_message_padding">4dp</dimen>
     <!-- Offset between bubbles in their stacked position. -->
     <dimen name="bubble_stack_offset">5dp</dimen>
-    <!-- How far offscreen the bubble stack rests. -->
-    <dimen name="bubble_stack_offscreen">5dp</dimen>
+    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
+    <dimen name="bubble_stack_offscreen">9dp</dimen>
     <!-- How far down the screen the stack starts. -->
-    <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
-    <!-- Size of image buttons in the bubble header -->
-    <dimen name="bubble_header_icon_size">48dp</dimen>
+    <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
     <!-- Space between the pointer triangle and the bubble expanded view -->
     <dimen name="bubble_pointer_margin">8dp</dimen>
     <!-- Height of the permission prompt shown with bubbles -->
@@ -1152,6 +1156,7 @@
          snap to the dismiss target. -->
     <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
     <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 66f1949..3727181 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -137,6 +137,7 @@
     <item type="id" name="scale_x_dynamicanimation_tag"/>
     <item type="id" name="scale_y_dynamicanimation_tag"/>
     <item type="id" name="physics_animator_tag"/>
+    <item type="id" name="target_animator_tag" />
 
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 19e682b..97e2f0f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -294,7 +294,7 @@
     <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_try_again">Try again</string>
     <!-- Content description for empty spaces that are not taken by the biometric dialog. Clicking on these areas will cancel authentication and dismiss the biometric dialog [CHAR LIMIT=NONE] -->
-    <string name="biometric_dialog_empty_space_description">Empty region, tap to cancel authentication</string>
+    <string name="biometric_dialog_empty_space_description">Tap to cancel authentication</string>
     <!-- Content description for the face icon when the device is not authenticating anymore [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_face_icon_description_idle">Please try again</string>
     <!-- Content description for the face icon when the device is authenticating [CHAR LIMIT=NONE] -->
@@ -305,6 +305,8 @@
     <string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
     <!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
     <string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
+    <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_authenticated">Authenticated</string>
 
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
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 82287873..00e8b53 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
@@ -225,14 +225,16 @@
                 runner = new IRecentsAnimationRunner.Stub() {
                     @Override
                     public void onAnimationStart(IRecentsAnimationController controller,
-                            RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                            Rect minimizedHomeBounds) {
+                            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                            Rect homeContentInsets, Rect minimizedHomeBounds) {
                         final RecentsAnimationControllerCompat controllerCompat =
                                 new RecentsAnimationControllerCompat(controller);
                         final RemoteAnimationTargetCompat[] appsCompat =
                                 RemoteAnimationTargetCompat.wrap(apps);
+                        final RemoteAnimationTargetCompat[] wallpapersCompat =
+                                RemoteAnimationTargetCompat.wrap(wallpapers);
                         animationHandler.onAnimationStart(controllerCompat, appsCompat,
-                                homeContentInsets, minimizedHomeBounds);
+                                wallpapersCompat, homeContentInsets, minimizedHomeBounds);
                     }
 
                     @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 3ae2df5b..2797042 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.shared.system;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
-import android.os.RemoteException;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
@@ -32,62 +33,132 @@
  * previously set listener.
  */
 public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
-    private List<IPinnedStackListener> mListeners = new ArrayList<>();
+    private List<PinnedStackListener> mListeners = new ArrayList<>();
 
     /** Adds a listener to receive updates from the WindowManagerService. */
-    public void addListener(IPinnedStackListener listener) {
+    public void addListener(PinnedStackListener listener) {
         mListeners.add(listener);
     }
 
     /** Removes a listener so it will no longer receive updates from the WindowManagerService. */
-    public void removeListener(IPinnedStackListener listener) {
+    public void removeListener(PinnedStackListener listener) {
         mListeners.remove(listener);
     }
 
     @Override
-    public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onListenerRegistered(IPinnedStackController controller) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onListenerRegistered(controller);
         }
     }
 
     @Override
-    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
-            listener.onMovementBoundsChanged(
-                    insetBounds, normalBounds, animatingBounds,
-                    fromImeAdjustment, fromShelfAdjustment, displayRotation);
+    public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment,
+                    fromShelfAdjustment);
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onImeVisibilityChanged(imeVisible, imeHeight);
         }
     }
 
     @Override
-    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
         }
     }
 
     @Override
-    public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onMinimizedStateChanged(boolean isMinimized) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onMinimizedStateChanged(isMinimized);
         }
     }
 
     @Override
-    public void onActionsChanged(ParceledListSlice actions) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onActionsChanged(ParceledListSlice actions) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onActionsChanged(actions);
         }
     }
+
+    @Override
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onSaveReentrySnapFraction(componentName, bounds);
+        }
+    }
+
+    @Override
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onResetReentrySnapFraction(componentName);
+        }
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onDisplayInfoChanged(displayInfo);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onConfigurationChanged();
+        }
+    }
+
+    @Override
+    public void onAspectRatioChanged(float aspectRatio) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onAspectRatioChanged(aspectRatio);
+        }
+    }
+
+    @Override
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
+        }
+    }
+
+    /**
+     * A counterpart of {@link IPinnedStackListener} with empty implementations.
+     * Subclasses can ignore those methods they do not intend to take action upon.
+     */
+    public static class PinnedStackListener {
+        public void onListenerRegistered(IPinnedStackController controller) {}
+
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {}
+
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+
+        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+        public void onMinimizedStateChanged(boolean isMinimized) {}
+
+        public void onActionsChanged(ParceledListSlice actions) {}
+
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {}
+
+        public void onResetReentrySnapFraction(ComponentName componentName) {}
+
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
+
+        public void onConfigurationChanged() {}
+
+        public void onAspectRatioChanged(float aspectRatio) {}
+
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {}
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 579858a..2c99c5c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -21,12 +21,12 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 public interface RecentsAnimationListener {
-
     /**
      * Called when the animation into Recents can start. This call is made on the binder thread.
      */
     void onAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds);
+            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+            Rect homeContentInsets, Rect minimizedHomeBounds);
 
     /**
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 61be076..02e509e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -45,9 +45,12 @@
         return new IRemoteAnimationRunner.Stub() {
             @Override
             public void onAnimationStart(RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
                 final RemoteAnimationTargetCompat[] appsCompat =
                         RemoteAnimationTargetCompat.wrap(apps);
+                final RemoteAnimationTargetCompat[] wallpapersCompat =
+                        RemoteAnimationTargetCompat.wrap(wallpapers);
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -59,7 +62,8 @@
                         }
                     }
                 };
-                remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback);
+                remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat,
+                        animationFinishedCallback);
             }
 
             @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5a85df9..33372f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.system;
 
 public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback);
+    void onAnimationStart(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback);
     void onAnimationCancelled();
 }
\ No newline at end of file
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 221782e..ca88f13 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
@@ -68,7 +68,7 @@
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
         final RemoteAnimationTargetCompat[] appsCompat =
-                new RemoteAnimationTargetCompat[apps.length];
+                new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
         for (int i = 0; i < apps.length; i++) {
             appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 9ba21a3..e80b437 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -142,7 +142,9 @@
     public static void applyParams(TransactionCompat t,
             SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
         t.setMatrix(params.surface, params.matrix);
-        t.setWindowCrop(params.surface, params.windowCrop);
+        if (params.windowCrop != null) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
         t.setAlpha(params.surface, params.alpha);
         t.setLayer(params.surface, params.layer);
         t.setCornerRadius(params.surface, params.cornerRadius);
@@ -187,14 +189,14 @@
          * @param surface The surface to modify.
          * @param alpha Alpha to apply.
          * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
+         * @param windowCrop Crop to apply, only applied if not {@code null}
          */
         public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius) {
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
-            this.windowCrop = new Rect(windowCrop);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
             this.layer = layer;
             this.cornerRadius = cornerRadius;
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 7757161..5ddf89c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -72,6 +72,13 @@
      */
     public void onSingleTaskDisplayDrawn(int displayId) { }
 
+    /**
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    public void onSingleTaskDisplayEmpty(int displayId) {}
+
     public void onTaskProfileLocked(int taskId, int userId) { }
     public void onTaskCreated(int taskId, ComponentName componentName) { }
     public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a7f4396..820057a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -209,6 +209,12 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+        mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+                0 /* unused */).sendToTarget();
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
         mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
     }
@@ -240,6 +246,7 @@
         private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
         private static final int ON_TASK_DISPLAY_CHANGED = 20;
         private static final int ON_TASK_LIST_UPDATED = 21;
+        private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22;
 
 
         public H(Looper looper) {
@@ -382,6 +389,13 @@
                         }
                         break;
                     }
+                    case ON_SINGLE_TASK_DISPLAY_EMPTY: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
+                                    msg.arg1);
+                        }
+                        break;
+                    }
                     case ON_TASK_DISPLAY_CHANGED: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 794c30a..9f1a1fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,12 +27,12 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.IPinnedStackListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 
 public class WindowManagerWrapper {
 
@@ -212,7 +212,7 @@
      * Adds a pinned stack listener, which will receive updates from the window manager service
      * along with any other pinned stack listeners that were added via this method.
      */
-    public void addPinnedStackListener(IPinnedStackListener listener) throws RemoteException {
+    public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
         mPinnedStackListenerForwarder.addListener(listener);
         WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
                 DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
@@ -221,7 +221,7 @@
     /**
      * Removes a pinned stack listener.
      */
-    public void removePinnedStackListener(IPinnedStackListener listener) {
+    public void removePinnedStackListener(PinnedStackListener listener) {
         mPinnedStackListenerForwarder.removeListener(listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index adcb7a1..f6b03c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,6 +24,9 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
 import java.util.Locale;
 
 public class CarrierText extends TextView {
@@ -83,7 +86,7 @@
                 com.android.internal.R.string.kg_text_message_separator);
         mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
                 mShowMissingSim);
-        mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setSelected(mShouldMarquee); // Allow marquee to work.
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 107d5cc..e1b723e 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 import java.util.List;
@@ -225,7 +226,7 @@
             // TODO(b/140034799)
             if (whitelistIpcs(() -> ConnectivityManager.from(mContext).isNetworkSupported(
                     ConnectivityManager.TYPE_MOBILE))) {
-                mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+                mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
                 mKeyguardUpdateMonitor.registerCallback(mCallback);
                 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
                 telephonyManager.listen(mPhoneStateListener,
@@ -298,7 +299,9 @@
                 }
             }
         }
-        if (allSimsMissing) {
+        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+        // This condition will also be true always when numSubs == 0
+        if (allSimsMissing && !anySimReadyAndInService) {
             if (numSubs != 0) {
                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
@@ -487,7 +490,7 @@
         }
 
         final boolean missingAndNotProvisioned =
-                !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
+                !Dependency.get(KeyguardUpdateMonitor.class).isDeviceProvisioned()
                         && (simState == IccCardConstants.State.ABSENT
                         || simState == IccCardConstants.State.PERM_DISABLED);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 979f3dc..1c30762 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -40,6 +40,7 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.util.EmergencyDialerConstants;
 
 /**
@@ -104,13 +105,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
     }
 
     @Override
@@ -186,7 +187,7 @@
                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
             }
         } else {
-            KeyguardUpdateMonitor.getInstance(mContext).reportEmergencyCallAction(
+            Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
                     true /* bypassHandler */);
             getContext().startActivityAsUser(INTENT_EMERGENCY_DIAL,
                     ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
@@ -201,7 +202,7 @@
             if (isInCall()) {
                 visible = true; // always show "return to call" if phone is off-hook
             } else {
-                final boolean simLocked = KeyguardUpdateMonitor.getInstance(mContext)
+                final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
                         .isSimPinVoiceSecure();
                 if (simLocked) {
                     // Some countries can't handle emergency calls while SIM is locked.
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
index e98ef06..225bebe 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
@@ -21,6 +21,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.R;
+
 public class EmergencyCarrierArea extends AlphaOptimizedLinearLayout {
 
     private CarrierText mCarrierText;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 517abac..d45603f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,6 +33,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 import java.util.Arrays;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
index 6a83c71..a78c293 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
@@ -23,6 +23,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.TextView;
 
+import com.android.systemui.R;
+
 /**
  * Replaces fancy colons with regular colons. Only works on TextViews.
  */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5097216..df0dc46 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -30,6 +30,7 @@
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0ec60e5..9380eb4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.phone.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index c2bbfbf..fe64142 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -32,6 +32,8 @@
 import android.view.WindowManager;
 import android.widget.Button;
 
+import com.android.systemui.R;
+
 /***
  * This button is used by the device with embedded SIM card to disable current carrier to unlock
  * the device with no cellular service.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 14ead04..2c8f238 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -35,6 +35,8 @@
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 import java.io.File;
@@ -106,7 +108,7 @@
 
     public KeyguardHostView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 037a8d3..a8b1451 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -31,6 +31,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.lang.ref.WeakReference;
@@ -85,7 +87,7 @@
     @Inject
     public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             ConfigurationController configurationController) {
-        this(context, attrs, KeyguardUpdateMonitor.getInstance(context), configurationController);
+        this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
     }
 
     public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
@@ -177,7 +179,7 @@
 
     @Override
     protected void onFinishInflate() {
-        boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setSelected(shouldMarquee); // This is required to ensure marquee works
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 6808c0f..12ea1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -24,6 +24,8 @@
 
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 /**
  * Displays a PIN pad for unlocking.
@@ -61,7 +63,7 @@
                         mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 96392156..eaaa3ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -37,6 +37,7 @@
 import android.widget.TextView.OnEditorActionListener;
 
 import com.android.internal.widget.TextViewInputDisabler;
+import com.android.systemui.R;
 
 import java.util.List;
 /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 56b38f7..297052f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -42,6 +42,8 @@
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 import java.util.List;
 
@@ -116,7 +118,7 @@
 
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAppearAnimationUtils = new AppearAnimationUtils(context,
                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index ecafc34..274f739 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -23,6 +23,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.R;
+
 /**
  * A Pin based Keyguard input view
  */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 454f446..8d167e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -47,8 +47,9 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.InjectionInflationController;
 
 public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
@@ -93,7 +94,7 @@
     private final SpringAnimation mSpringAnimation;
     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
 
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private float mLastTouchY = -1;
@@ -129,12 +130,12 @@
         super(context, attrs, defStyle);
         mSecurityModel = new KeyguardSecurityModel(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mInjectionInflationController =  new InjectionInflationController(
             SystemUIFactory.getInstance().getRootComponent());
-        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
         mViewConfiguration = ViewConfiguration.get(context);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
     }
 
     public void setSecurityCallback(SecurityCallback callback) {
@@ -271,7 +272,7 @@
      */
     private void updateBiometricRetry() {
         SecurityMode securityMode = getSecurityMode();
-        mSwipeUpToRetry = mUnlockMethodCache.isFaceAuthEnabled()
+        mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled()
                 && securityMode != SecurityMode.SimPin
                 && securityMode != SecurityMode.SimPuk
                 && securityMode != SecurityMode.None;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 16e9ffc..bb89959 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
 
 public class KeyguardSecurityModel {
 
@@ -57,7 +58,7 @@
     }
 
     SecurityMode getSecurityMode(int userId) {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                 monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index e6a0250..24da3ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -32,6 +32,7 @@
 import android.widget.ViewFlipper;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 /**
  * Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 69da990..9c0a71c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -41,6 +41,8 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 /**
  * Displays a PIN pad for unlocking.
@@ -111,8 +113,8 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_sim_pin_instructions);
         } else {
-            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
-                    getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+                    .getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
             msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
             if (info != null) {
@@ -149,7 +151,7 @@
     }
 
     private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
@@ -222,7 +224,7 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         resetState();
     }
 
@@ -233,7 +235,7 @@
             mSimUnlockProgressDialog.dismiss();
             mSimUnlockProgressDialog = null;
         }
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     /**
@@ -337,7 +339,7 @@
                             resetPasswordText(true /* animate */,
                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
-                                KeyguardUpdateMonitor.getInstance(getContext())
+                                Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
                                 mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 27f71d1..fc888e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -40,6 +40,8 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 
 /**
@@ -171,8 +173,8 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
         } else {
-            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
-                    getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+                    .getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
             if (info != null) {
@@ -202,7 +204,7 @@
     }
 
     private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
@@ -271,14 +273,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         resetState();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     @Override
@@ -408,7 +410,7 @@
                             resetPasswordText(true /* animate */,
                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
-                                KeyguardUpdateMonitor.getInstance(getContext())
+                                Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
                                 mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 37e89c0..63da533 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -40,6 +40,7 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -198,7 +199,7 @@
         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
         onSliceContentChanged();
 
-        boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
         refreshFormat();
         updateOwnerInfo();
@@ -314,14 +315,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
@@ -441,7 +442,7 @@
     }
 
     private boolean shouldShowLogout() {
-        return KeyguardUpdateMonitor.getInstance(mContext).isLogoutEnabled()
+        return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1714cd5..720074b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -18,6 +18,8 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -37,13 +39,13 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
-import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
@@ -94,9 +96,9 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -113,6 +115,9 @@
 import java.util.TimeZone;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
  * the up to date information as well as a registration for callbacks that care
@@ -162,6 +167,8 @@
     private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
     private static final int MSG_TELEPHONY_CAPABLE = 338;
     private static final int MSG_TIMEZONE_UPDATE = 339;
+    private static final int MSG_USER_STOPPED = 340;
+    private static final int MSG_USER_REMOVED = 341;
 
     /** Biometric authentication state: Not listening. */
     private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -210,8 +217,6 @@
         }
     }
 
-    private static KeyguardUpdateMonitor sInstance;
-
     private final Context mContext;
     private final boolean mIsPrimaryUser;
     HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
@@ -288,113 +293,7 @@
         }
     };
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_TIME_UPDATE:
-                    handleTimeUpdate();
-                    break;
-                case MSG_TIMEZONE_UPDATE:
-                    handleTimeZoneUpdate((String) msg.obj);
-                    break;
-                case MSG_BATTERY_UPDATE:
-                    handleBatteryUpdate((BatteryStatus) msg.obj);
-                    break;
-                case MSG_SIM_STATE_CHANGE:
-                    handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
-                    break;
-                case MSG_RINGER_MODE_CHANGED:
-                    handleRingerModeChange(msg.arg1);
-                    break;
-                case MSG_PHONE_STATE_CHANGED:
-                    handlePhoneStateChanged((String) msg.obj);
-                    break;
-                case MSG_DEVICE_PROVISIONED:
-                    handleDeviceProvisioned();
-                    break;
-                case MSG_DPM_STATE_CHANGED:
-                    handleDevicePolicyManagerStateChanged();
-                    break;
-                case MSG_USER_SWITCHING:
-                    handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
-                    break;
-                case MSG_USER_SWITCH_COMPLETE:
-                    handleUserSwitchComplete(msg.arg1);
-                    break;
-                case MSG_KEYGUARD_RESET:
-                    handleKeyguardReset();
-                    break;
-                case MSG_KEYGUARD_BOUNCER_CHANGED:
-                    handleKeyguardBouncerChanged(msg.arg1);
-                    break;
-                case MSG_BOOT_COMPLETED:
-                    handleBootCompleted();
-                    break;
-                case MSG_USER_INFO_CHANGED:
-                    handleUserInfoChanged(msg.arg1);
-                    break;
-                case MSG_REPORT_EMERGENCY_CALL_ACTION:
-                    handleReportEmergencyCallAction();
-                    break;
-                case MSG_STARTED_GOING_TO_SLEEP:
-                    handleStartedGoingToSleep(msg.arg1);
-                    break;
-                case MSG_FINISHED_GOING_TO_SLEEP:
-                    handleFinishedGoingToSleep(msg.arg1);
-                    break;
-                case MSG_STARTED_WAKING_UP:
-                    Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
-                    handleStartedWakingUp();
-                    Trace.endSection();
-                    break;
-                case MSG_FACE_UNLOCK_STATE_CHANGED:
-                    Trace.beginSection(
-                            "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
-                    handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
-                    Trace.endSection();
-                    break;
-                case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
-                    handleSimSubscriptionInfoChanged();
-                    break;
-                case MSG_AIRPLANE_MODE_CHANGED:
-                    handleAirplaneModeChanged();
-                    break;
-                case MSG_SERVICE_STATE_CHANGE:
-                    handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
-                    break;
-                case MSG_SCREEN_TURNED_ON:
-                    handleScreenTurnedOn();
-                    break;
-                case MSG_SCREEN_TURNED_OFF:
-                    Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
-                    handleScreenTurnedOff();
-                    Trace.endSection();
-                    break;
-                case MSG_DREAMING_STATE_CHANGED:
-                    handleDreamingStateChanged(msg.arg1);
-                    break;
-                case MSG_USER_UNLOCKED:
-                    handleUserUnlocked();
-                    break;
-                case MSG_ASSISTANT_STACK_CHANGED:
-                    setAssistantVisible((boolean) msg.obj);
-                    break;
-                case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
-                    updateBiometricListeningState();
-                    break;
-                case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
-                    updateLogoutEnabled();
-                    break;
-                case MSG_TELEPHONY_CAPABLE:
-                    updateTelephonyCapable((boolean) msg.obj);
-                    break;
-                default:
-                    super.handleMessage(msg);
-                    break;
-            }
-        }
-    };
+    private final Handler mHandler;
 
     private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray();
     private BiometricManager mBiometricManager;
@@ -427,6 +326,7 @@
                 }
             };
 
+    private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
     private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
@@ -435,7 +335,6 @@
 
     private static int sCurrentUser;
     private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState;
-    private static boolean sDisableHandlerCheckForTesting;
 
     public synchronized static void setCurrentUser(int currentUser) {
         sCurrentUser = currentUser;
@@ -1207,7 +1106,14 @@
                     .equals(action)) {
                 mHandler.sendEmptyMessage(MSG_DPM_STATE_CHANGED);
             } else if (ACTION_USER_UNLOCKED.equals(action)) {
-                mHandler.sendEmptyMessage(MSG_USER_UNLOCKED);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_UNLOCKED,
+                        getSendingUserId(), 0));
+            } else if (ACTION_USER_STOPPED.equals(action)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_STOPPED,
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1), 0));
+            } else if (ACTION_USER_REMOVED.equals(action)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1), 0));
             }
         }
     };
@@ -1468,13 +1374,6 @@
         }
     }
 
-    public static KeyguardUpdateMonitor getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new KeyguardUpdateMonitor(context);
-        }
-        return sInstance;
-    }
-
     protected void handleStartedWakingUp() {
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         checkIsHandlerThread();
@@ -1558,8 +1457,9 @@
         }
     }
 
-    private void handleUserUnlocked() {
+    private void handleUserUnlocked(int userId) {
         checkIsHandlerThread();
+        mUserIsUnlocked.put(userId, true);
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1569,13 +1469,138 @@
         }
     }
 
+    private void handleUserStopped(int userId) {
+        checkIsHandlerThread();
+        mUserIsUnlocked.put(userId, mUserManager.isUserUnlocked(userId));
+    }
+
+    private void handleUserRemoved(int userId) {
+        checkIsHandlerThread();
+        mUserIsUnlocked.delete(userId);
+    }
+
     @VisibleForTesting
-    protected KeyguardUpdateMonitor(Context context) {
+    @Inject
+    protected KeyguardUpdateMonitor(Context context, @Named(MAIN_LOOPER_NAME) Looper mainLooper) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
 
+        mHandler = new Handler(mainLooper) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_TIME_UPDATE:
+                        handleTimeUpdate();
+                        break;
+                    case MSG_TIMEZONE_UPDATE:
+                        handleTimeZoneUpdate((String) msg.obj);
+                        break;
+                    case MSG_BATTERY_UPDATE:
+                        handleBatteryUpdate((BatteryStatus) msg.obj);
+                        break;
+                    case MSG_SIM_STATE_CHANGE:
+                        handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
+                        break;
+                    case MSG_RINGER_MODE_CHANGED:
+                        handleRingerModeChange(msg.arg1);
+                        break;
+                    case MSG_PHONE_STATE_CHANGED:
+                        handlePhoneStateChanged((String) msg.obj);
+                        break;
+                    case MSG_DEVICE_PROVISIONED:
+                        handleDeviceProvisioned();
+                        break;
+                    case MSG_DPM_STATE_CHANGED:
+                        handleDevicePolicyManagerStateChanged();
+                        break;
+                    case MSG_USER_SWITCHING:
+                        handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
+                        break;
+                    case MSG_USER_SWITCH_COMPLETE:
+                        handleUserSwitchComplete(msg.arg1);
+                        break;
+                    case MSG_KEYGUARD_RESET:
+                        handleKeyguardReset();
+                        break;
+                    case MSG_KEYGUARD_BOUNCER_CHANGED:
+                        handleKeyguardBouncerChanged(msg.arg1);
+                        break;
+                    case MSG_BOOT_COMPLETED:
+                        handleBootCompleted();
+                        break;
+                    case MSG_USER_INFO_CHANGED:
+                        handleUserInfoChanged(msg.arg1);
+                        break;
+                    case MSG_REPORT_EMERGENCY_CALL_ACTION:
+                        handleReportEmergencyCallAction();
+                        break;
+                    case MSG_STARTED_GOING_TO_SLEEP:
+                        handleStartedGoingToSleep(msg.arg1);
+                        break;
+                    case MSG_FINISHED_GOING_TO_SLEEP:
+                        handleFinishedGoingToSleep(msg.arg1);
+                        break;
+                    case MSG_STARTED_WAKING_UP:
+                        Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
+                        handleStartedWakingUp();
+                        Trace.endSection();
+                        break;
+                    case MSG_FACE_UNLOCK_STATE_CHANGED:
+                        Trace.beginSection(
+                                "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
+                        handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
+                        Trace.endSection();
+                        break;
+                    case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
+                        handleSimSubscriptionInfoChanged();
+                        break;
+                    case MSG_AIRPLANE_MODE_CHANGED:
+                        handleAirplaneModeChanged();
+                        break;
+                    case MSG_SERVICE_STATE_CHANGE:
+                        handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
+                        break;
+                    case MSG_SCREEN_TURNED_ON:
+                        handleScreenTurnedOn();
+                        break;
+                    case MSG_SCREEN_TURNED_OFF:
+                        Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
+                        handleScreenTurnedOff();
+                        Trace.endSection();
+                        break;
+                    case MSG_DREAMING_STATE_CHANGED:
+                        handleDreamingStateChanged(msg.arg1);
+                        break;
+                    case MSG_USER_UNLOCKED:
+                        handleUserUnlocked(msg.arg1);
+                        break;
+                    case MSG_USER_STOPPED:
+                        handleUserStopped(msg.arg1);
+                        break;
+                    case MSG_USER_REMOVED:
+                        handleUserRemoved(msg.arg1);
+                        break;
+                    case MSG_ASSISTANT_STACK_CHANGED:
+                        setAssistantVisible((boolean) msg.obj);
+                        break;
+                    case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
+                        updateBiometricListeningState();
+                        break;
+                    case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+                        updateLogoutEnabled();
+                        break;
+                    case MSG_TELEPHONY_CAPABLE:
+                        updateTelephonyCapable((boolean) msg.obj);
+                        break;
+                    default:
+                        super.handleMessage(msg);
+                        break;
+                }
+            }
+        };
+
         // Since device can't be un-provisioned, we only need to register a content observer
         // to update mDeviceProvisioned when we are...
         if (!mDeviceProvisioned) {
@@ -1612,6 +1637,8 @@
         allUserFilter.addAction(ACTION_FACE_UNLOCK_STOPPED);
         allUserFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         allUserFilter.addAction(ACTION_USER_UNLOCKED);
+        allUserFilter.addAction(ACTION_USER_STOPPED);
+        allUserFilter.addAction(ACTION_USER_REMOVED);
         context.registerReceiverAsUser(mBroadcastAllReceiver, UserHandle.ALL, allUserFilter,
                 null, mHandler);
 
@@ -1666,6 +1693,8 @@
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         mUserManager = context.getSystemService(UserManager.class);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
+        int user = ActivityManager.getCurrentUser();
+        mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateAirplaneModeState();
@@ -1709,6 +1738,19 @@
     }
 
     /**
+     * If a user is encrypted or not.
+     * This is NOT related to the lock screen being visible or not.
+     *
+     * @param userId The user.
+     * @return {@code true} when encrypted.
+     * @see UserManager#isUserUnlocked()
+     * @see Intent#ACTION_USER_UNLOCKED
+     */
+    public boolean isUserUnlocked(int userId) {
+        return mUserIsUnlocked.get(userId);
+    }
+
+    /**
      * Called whenever passive authentication is requested or aborted by a sensor.
      *
      * @param active If the interrupt started or ended.
@@ -1777,7 +1819,10 @@
         return shouldListen;
     }
 
-    private boolean shouldListenForFace() {
+    /**
+     * If face auth is allows to scan on this exact moment.
+     */
+    public boolean shouldListenForFace() {
         final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2297,7 +2342,7 @@
     }
 
     private boolean resolveNeedsSlowUnlockTransition() {
-        if (mUserManager.isUserUnlocked(getCurrentUser())) {
+        if (isUserUnlocked(getCurrentUser())) {
             return false;
         }
         Intent homeIntent = new Intent(Intent.ACTION_MAIN)
@@ -2706,35 +2751,12 @@
     }
 
     private void checkIsHandlerThread() {
-        if (sDisableHandlerCheckForTesting) {
-            return;
-        }
         if (!mHandler.getLooper().isCurrentThread()) {
             Log.wtf(TAG, "must call on mHandler's thread "
                     + mHandler.getLooper().getThread() + ", not " + Thread.currentThread());
         }
     }
 
-    /**
-     * Turn off the handler check for testing.
-     *
-     * This is necessary because currently tests are not too careful about which thread they call
-     * into this class on.
-     *
-     * Note that this must be called before scheduling any work involving KeyguardUpdateMonitor
-     * instances.
-     *
-     * TODO: fix the tests and remove this.
-     */
-    @VisibleForTesting
-    public static void disableHandlerCheckForTesting(Instrumentation instrumentation) {
-        Preconditions.checkNotNull(instrumentation, "Must only call this method in tests!");
-        // Don't need synchronization here *if* the callers follow the contract and call this only
-        // before scheduling work for KeyguardUpdateMonitor on other threads, because the scheduling
-        // of that work forces a happens-before relationship.
-        sDisableHandlerCheckForTesting = true;
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
         pw.println("  SIM States:");
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 1edb57e..b0457fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -29,6 +29,7 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 public class NumPadKey extends ViewGroup {
     // list of "ABC", etc per digit, starting with '0'
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index b21bcc9..409ae3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -42,6 +42,8 @@
 import android.view.animation.Interpolator;
 import android.widget.EditText;
 
+import com.android.systemui.R;
+
 import java.util.ArrayList;
 import java.util.Stack;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index 9c5242c..eba2400 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 8e81327..3a2fbe5 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
index 7485d33..d44d89e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
@@ -24,7 +24,7 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.keyguard.R;
+import com.android.systemui.R;
 
 /**
  * Positions clock faces (analog, digital, typographic) and handles pixel shifting
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
index 98679ade..c81935a 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextView;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
index 95f1004..34c041b 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
@@ -21,7 +21,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-import com.android.keyguard.R;
+import com.android.systemui.R;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
index 60ca945..b304074 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
@@ -20,6 +20,7 @@
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
 
 /**
  * Computes preferred position of clock by considering height of status bar and lock icon.
@@ -40,10 +41,10 @@
     private float mDarkAmount;
 
     SmallClockPosition(Resources res) {
-        this(res.getDimensionPixelSize(com.android.keyguard.R.dimen.status_bar_height),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_padding),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_height),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.burn_in_prevention_offset_y)
+        this(res.getDimensionPixelSize(R.dimen.status_bar_height),
+                res.getDimensionPixelSize(R.dimen.keyguard_lock_padding),
+                res.getDimensionPixelSize(R.dimen.keyguard_lock_height),
+                res.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
new file mode 100644
index 0000000..2c8a672
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import android.app.Activity;
+
+import com.android.systemui.tuner.TunerActivity;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Services and Activities that are injectable should go here.
+ */
+@Module
+public abstract class ActivityBinder {
+    /** Inject into TunerActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TunerActivity.class)
+    public abstract Activity bindTunerActivity(TunerActivity activity);
+
+    /** Inject into ForegroundServicesDialog. */
+    @Binds
+    @IntoMap
+    @ClassKey(ForegroundServicesDialog.class)
+    public abstract Activity bindForegroundServicesDialog(ForegroundServicesDialog activity);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
new file mode 100644
index 0000000..3b35c61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module that collects related sub-modules together.
+ */
+@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class})
+public abstract class ComponentBinder {
+    /** */
+    @Binds
+    public abstract ContextComponentHelper bindComponentHelper(
+            ContextComponentResolver componentHelper);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
index 8fabe7a..2cf0f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
@@ -16,10 +16,19 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
+import android.app.Service;
+
 /**
  * Interface necessary to make Dagger happy. See {@link ContextComponentResolver}.
  */
 public interface ContextComponentHelper {
     /** Turns a classname into an instance of the class or returns null. */
-    <T> T resolve(String className);
+    Activity resolveActivity(String className);
+
+    /** Turns a classname into an instance of the class or returns null. */
+    Service resolveService(String className);
+
+    /** Turns a classname into an instance of the class or returns null. */
+    SystemUI resolveSystemUI(String className);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
index 09bccd9..9952632 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
@@ -16,33 +16,65 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
+import android.app.Service;
+
 import java.util.Map;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
+import javax.inject.Singleton;
 
 /**
  * Used during Service and Activity instantiation to make them injectable.
  */
+@Singleton
 public class ContextComponentResolver implements ContextComponentHelper {
-    private final Map<Class<?>, Provider<Object>> mCreators;
+    private final Map<Class<?>, Provider<Activity>> mActivityCreators;
+    private final Map<Class<?>, Provider<Service>> mServiceCreators;
+    private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators;
 
     @Inject
-    ContextComponentResolver(Map<Class<?>, Provider<Object>> creators) {
-        mCreators = creators;
+    ContextComponentResolver(
+            Map<Class<?>, Provider<Activity>> activityCreators,
+            Map<Class<?>, Provider<Service>> serviceCreators,
+            Map<Class<?>, Provider<SystemUI>> systemUICreators) {
+        mActivityCreators = activityCreators;
+        mServiceCreators = serviceCreators;
+        mSystemUICreators = systemUICreators;
     }
 
     /**
-     * Looks up the class name to see if Dagger has an instance of it.
+     * Looks up the Activity class name to see if Dagger has an instance of it.
      */
     @Override
-    public <T> T resolve(String className) {
-        for (Map.Entry<Class<?>, Provider<Object>> p : mCreators.entrySet()) {
-            if (p.getKey().getName().equals(className)) {
-                return (T) p.getValue().get();
-            }
-        }
+    public Activity resolveActivity(String className) {
+        return resolve(className, mActivityCreators);
+    }
 
-        return null;
+    /**
+     * Looks up the Service class name to see if Dagger has an instance of it.
+     */
+    @Override
+    public Service resolveService(String className) {
+        return resolve(className, mServiceCreators);
+    }
+
+    /**
+     * Looks up the SystemUI class name to see if Dagger has an instance of it.
+     */
+    @Override
+    public SystemUI resolveSystemUI(String className) {
+        return resolve(className, mSystemUICreators);
+    }
+
+    private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
+        try {
+            Class<?> clazz = Class.forName(className);
+            Provider<T> provider = creators.get(clazz);
+            return provider == null ? null : provider.get();
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index f9452e2..37bb54c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,8 +15,8 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.NightDisplayListener;
@@ -30,6 +30,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.Preconditions;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.appops.AppOpsController;
@@ -96,7 +97,7 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -110,14 +111,13 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.HashMap;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -141,8 +141,11 @@
  * they have no clients they should not have any registered resources like bound
  * services, registered receivers, etc.
  */
-public class Dependency extends SystemUI {
-    private static final String TAG = "Dependency";
+public class Dependency {
+    /**
+     * Key for getting a the main looper.
+     */
+    public static final String MAIN_LOOPER_NAME = "main_looper";
 
     /**
      * Key for getting a background Looper for background work.
@@ -177,6 +180,10 @@
      */
     public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME);
     /**
+     * Key for getting a mainer Looper.
+     */
+    public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME);
+    /**
      * Key for getting a background Handler for background work.
      */
     public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>(BG_HANDLER_NAME);
@@ -214,7 +221,8 @@
     @Inject Lazy<FlashlightController> mFlashlightController;
     @Inject Lazy<UserSwitcherController> mUserSwitcherController;
     @Inject Lazy<UserInfoController> mUserInfoController;
-    @Inject Lazy<KeyguardMonitor> mKeyguardMonitor;
+    @Inject Lazy<KeyguardStateController> mKeyguardMonitor;
+    @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
     @Inject Lazy<BatteryController> mBatteryController;
     @Inject Lazy<NightDisplayListener> mNightDisplayListener;
     @Inject Lazy<ManagedProfileController> mManagedProfileController;
@@ -291,6 +299,7 @@
     @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
     @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
+    @Inject @Named(MAIN_LOOPER_NAME) Lazy<Looper> mMainLooper;
     @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Nullable
@@ -306,18 +315,22 @@
     @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<FalsingManager> mFalsingManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
+    @Inject Lazy<AlarmManager> mAlarmManager;
 
     @Inject
     public Dependency() {
     }
 
-    @Override
-    public void start() {
+    /**
+     * Initialize Depenency.
+     */
+    protected void start() {
         // TODO: Think about ways to push these creation rules out of Dependency to cut down
         // on imports.
         mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
         mProviders.put(BG_LOOPER, mBgLooper::get);
         mProviders.put(BG_HANDLER, mBgHandler::get);
+        mProviders.put(MAIN_LOOPER, mMainLooper::get);
         mProviders.put(MAIN_HANDLER, mMainHandler::get);
         mProviders.put(ActivityStarter.class, mActivityStarter::get);
         mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get);
@@ -342,7 +355,9 @@
 
         mProviders.put(FlashlightController.class, mFlashlightController::get);
 
-        mProviders.put(KeyguardMonitor.class, mKeyguardMonitor::get);
+        mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get);
+
+        mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
 
         mProviders.put(UserSwitcherController.class, mUserSwitcherController::get);
 
@@ -485,6 +500,7 @@
         mProviders.put(INotificationManager.class, mINotificationManager::get);
         mProviders.put(FalsingManager.class, mFalsingManager::get);
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
+        mProviders.put(AlarmManager.class, mAlarmManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
@@ -495,10 +511,14 @@
         sDependency = this;
     }
 
-    @Override
-    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
+    static void staticDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        sDependency.dump(fd, pw, args);
+    }
 
+    /**
+     * {@see SystemUI.dump}
+     */
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         // Make sure that the DumpController gets added to mDependencies, as they are only added
         // with Dependency#get.
         getDependency(DumpController.class);
@@ -519,9 +539,11 @@
                 .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
     }
 
-    @Override
+    protected static void staticOnConfigurationChanged(Configuration newConfig) {
+        sDependency.onConfigurationChanged(newConfig);
+    }
+
     protected synchronized void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
         mDependencies.values().stream().filter(obj -> obj instanceof ConfigurationChangedReceiver)
                 .forEach(o -> ((ConfigurationChangedReceiver) o).onConfigurationChanged(newConfig));
     }
@@ -575,20 +597,6 @@
     }
 
     /**
-     * Used in separate processes (like tuner settings) to init the dependencies.
-     */
-    public static void initDependencies(Context context) {
-        if (sDependency != null) return;
-        Dependency d = new Dependency();
-        SystemUIFactory.getInstance().getRootComponent()
-                .createDependency()
-                .createSystemUI(d);
-        d.mContext = context;
-        d.mComponents = new HashMap<>();
-        d.start();
-    }
-
-    /**
      * Used in separate process teardown to ensure the context isn't leaked.
      *
      * TODO: Remove once PreferenceFragment doesn't reference getActivity()
@@ -639,15 +647,4 @@
     public interface DependencyInjector {
         void createSystemUI(Dependency dependency);
     }
-
-    public static class DependencyCreator implements Injector {
-        @Override
-        public SystemUI apply(Context context) {
-            Dependency dependency = new Dependency();
-            SystemUIFactory.getInstance().getRootComponent()
-                    .createDependency()
-                    .createSystemUI(dependency);
-            return dependency;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index 4df7f0d..818b5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -48,8 +48,8 @@
 import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
 import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
@@ -146,7 +146,8 @@
     /**
      */
     @Binds
-    public abstract KeyguardMonitor provideKeyguardMonitor(KeyguardMonitorImpl controllerImpl);
+    public abstract KeyguardStateController provideKeyguardMonitor(
+            KeyguardStateControllerImpl controllerImpl);
 
     /**
      */
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index 3e2010e..239cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -19,9 +19,11 @@
 import static com.android.systemui.Dependency.BG_HANDLER_NAME;
 import static com.android.systemui.Dependency.BG_LOOPER_NAME;
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
@@ -87,6 +89,14 @@
         return thread.getLooper();
     }
 
+    /** Main Looper */
+    @Singleton
+    @Provides
+    @Named(MAIN_LOOPER_NAME)
+    public Looper provideMainLooper() {
+        return Looper.getMainLooper();
+    }
+
     @Singleton
     @Provides
     @Named(BG_HANDLER_NAME)
@@ -97,8 +107,8 @@
     @Singleton
     @Provides
     @Named(MAIN_HANDLER_NAME)
-    public Handler provideMainHandler() {
-        return new Handler(Looper.getMainLooper());
+    public Handler provideMainHandler(@Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+        return new Handler(mainLooper);
     }
 
     @Singleton
@@ -221,4 +231,11 @@
             @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
         return new DeviceProvisionedControllerImpl(context, mainHandler);
     }
+
+    /** */
+    @Singleton
+    @Provides
+    public AlarmManager provideAlarmManager(Context context) {
+        return context.getSystemService(AlarmManager.class);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
new file mode 100644
index 0000000..0e079e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Extends the lifetime of foreground notification services such that they show for at least
+ * five seconds
+ */
+public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {
+
+    private static final String TAG = "FGSLifetimeExtender";
+    @VisibleForTesting
+    static final int MIN_FGS_TIME_MS = 5000;
+
+    private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
+    private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+
+    public ForegroundServiceLifetimeExtender() {
+    }
+
+    @Override
+    public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
+        mNotificationSafeToRemoveCallback = callback;
+    }
+
+    @Override
+    public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
+        if ((entry.notification.getNotification().flags
+                & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
+            return false;
+        }
+
+        long currentTime = System.currentTimeMillis();
+        return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
+    }
+
+    @Override
+    public boolean shouldExtendLifetimeForPendingNotification(
+            @NonNull NotificationEntry entry) {
+        return shouldExtendLifetime(entry);
+    }
+
+    @Override
+    public void setShouldManageLifetime(
+            @NonNull NotificationEntry entry, boolean shouldManage) {
+        if (!shouldManage) {
+            mManagedEntries.remove(entry);
+            return;
+        }
+
+        mManagedEntries.add(entry);
+
+        Runnable r = () -> {
+            if (mManagedEntries.contains(entry)) {
+                mManagedEntries.remove(entry);
+                if (mNotificationSafeToRemoveCallback != null) {
+                    mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
+                }
+            }
+        };
+        mHandler.postDelayed(r, MIN_FGS_TIME_MS);
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 96b62ac..f9d8771 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -50,12 +50,12 @@
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
-                addNotification(entry.notification, entry.importance);
+                addNotification(entry.notification, entry.getImportance());
             }
 
             @Override
             public void onPostEntryUpdated(NotificationEntry entry) {
-                updateNotification(entry.notification, entry.importance);
+                updateNotification(entry.notification, entry.getImportance());
             }
 
             @Override
@@ -66,6 +66,9 @@
                 removeNotification(entry.notification);
             }
         });
+
+        notificationEntryManager.addNotificationLifetimeExtender(
+                new ForegroundServiceLifetimeExtender());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
index cb9523f..710980a 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
@@ -44,6 +44,8 @@
 
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * Show a list of currently running foreground services (supplied by the caller)
  * that the user can tap through to their application details.
@@ -72,10 +74,14 @@
                 }
             };
 
+    @Inject
+    ForegroundServicesDialog() {
+        super();
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Dependency.initDependencies(getApplicationContext());
 
         mMetricsLogger = Dependency.get(MetricsLogger.class);
 
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index bd91333..1c0e0b3 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.HandlerThread;
+import android.os.Trace;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 import android.util.Size;
@@ -48,6 +49,7 @@
     private static final int DELAY_FINISH_RENDERING = 1000;
     private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
     private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
+    private static final boolean DEBUG = true;
     private HandlerThread mWorker;
 
     @Override
@@ -125,6 +127,10 @@
         @Override
         public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
             if (!mNeedTransition) return;
+            if (DEBUG) {
+                Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode
+                        + ", duration=" + animationDuration);
+            }
             mWorker.getThreadHandler().post(
                     () -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration));
             if (inAmbientMode && animationDuration == 0) {
@@ -184,17 +190,32 @@
 
         @Override
         public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.d(TAG, "onSurfaceRedrawNeeded: mNeedRedraw=" + mNeedRedraw);
+            }
+
             mWorker.getThreadHandler().post(() -> {
                 if (mNeedRedraw) {
-                    preRender();
-                    requestRender();
-                    postRender();
+                    drawFrame();
                     mNeedRedraw = false;
                 }
             });
         }
 
         @Override
+        public void onVisibilityChanged(boolean visible) {
+            if (DEBUG) {
+                Log.d(TAG, "wallpaper visibility changes: " + visible);
+            }
+        }
+
+        private void drawFrame() {
+            preRender();
+            requestRender();
+            postRender();
+        }
+
+        @Override
         public void onStatePostChange() {
             // When back to home, we try to release EGL, which is preserved in lock screen or aod.
             if (mController.getState() == StatusBarState.SHADE) {
@@ -205,7 +226,9 @@
         @Override
         public void preRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#preRender");
             preRenderInternal();
+            Trace.endSection();
         }
 
         private void preRenderInternal() {
@@ -240,7 +263,9 @@
         @Override
         public void requestRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#requestRender");
             requestRenderInternal();
+            Trace.endSection();
         }
 
         private void requestRenderInternal() {
@@ -263,8 +288,10 @@
         @Override
         public void postRender() {
             // This method should only be invoked from worker thread.
+            Trace.beginSection("ImageWallpaper#postRender");
             notifyWaitingThread();
             scheduleFinishRendering();
+            Trace.endSection();
         }
 
         private void notifyWaitingThread() {
@@ -289,12 +316,14 @@
         }
 
         private void finishRendering() {
+            Trace.beginSection("ImageWallpaper#finishRendering");
             if (mEglHelper != null) {
                 mEglHelper.destroyEglSurface();
                 if (!needPreserveEglContext()) {
                     mEglHelper.destroyEglContext();
                 }
             }
+            Trace.endSection();
         }
 
         private boolean needPreserveEglContext() {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index f38b4f2..3e068b0 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -25,9 +25,6 @@
 import static com.android.systemui.tuner.TunablePadding.FLAG_END;
 import static com.android.systemui.tuner.TunablePadding.FLAG_START;
 
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.annotation.Dimension;
 import android.app.ActivityManager;
 import android.app.Fragment;
@@ -52,7 +49,6 @@
 import android.provider.Settings.Secure;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.MathUtils;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
@@ -64,9 +60,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -78,10 +71,7 @@
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.SecureSetting;
-import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NavigationBarTransitions;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunerService;
@@ -95,8 +85,7 @@
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
  */
-public class ScreenDecorations extends SystemUI implements Tunable,
-        NavigationBarTransitions.DarkIntensityListener {
+public class ScreenDecorations extends SystemUI implements Tunable {
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -120,15 +109,11 @@
     private float mDensity;
     private WindowManager mWindowManager;
     private int mRotation;
-    private boolean mAssistHintVisible;
     private DisplayCutoutView mCutoutTop;
     private DisplayCutoutView mCutoutBottom;
     private SecureSetting mColorInversionSetting;
     private boolean mPendingRotationChange;
     private Handler mHandler;
-    private boolean mAssistHintBlocked = false;
-    private boolean mIsReceivingNavBarColor = false;
-    private boolean mInGesturalMode;
 
     /**
      * Converts a set of {@link Rect}s into a {@link Region}
@@ -153,160 +138,6 @@
         mHandler.post(this::startOnScreenDecorationsThread);
         setupStatusBarPaddingIfNeeded();
         putComponent(ScreenDecorations.class, this);
-        mInGesturalMode = QuickStepContract.isGesturalMode(
-                Dependency.get(NavigationModeController.class)
-                        .addListener(this::handleNavigationModeChange));
-    }
-
-    @VisibleForTesting
-    void handleNavigationModeChange(int navigationMode) {
-        if (!mHandler.getLooper().isCurrentThread()) {
-            mHandler.post(() -> handleNavigationModeChange(navigationMode));
-            return;
-        }
-        boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
-        if (mInGesturalMode != inGesturalMode) {
-            mInGesturalMode = inGesturalMode;
-
-            if (mInGesturalMode && mOverlay == null) {
-                setupDecorations();
-                if (mOverlay != null) {
-                    updateLayoutParams();
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns an animator that animates the given view from start to end over durationMs. Start and
-     * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
-     * overshoot.
-     */
-    Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
-            Interpolator interpolator) {
-        // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
-        float scaleStart = MathUtils.lerp(2f, 1f, start);
-        float scaleEnd = MathUtils.lerp(2f, 1f, end);
-        Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
-        Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
-        float translationStart = MathUtils.lerp(0.2f, 0f, start);
-        float translationEnd = MathUtils.lerp(0.2f, 0f, end);
-        int xDirection = isLeft ? -1 : 1;
-        Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
-                xDirection * translationStart * view.getWidth(),
-                xDirection * translationEnd * view.getWidth());
-        Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
-                translationStart * view.getHeight(), translationEnd * view.getHeight());
-
-        AnimatorSet set = new AnimatorSet();
-        set.play(scaleX).with(scaleY);
-        set.play(scaleX).with(translateX);
-        set.play(scaleX).with(translateY);
-        set.setDuration(durationMs);
-        set.setInterpolator(interpolator);
-        return set;
-    }
-
-    private void fade(View view, boolean fadeIn, boolean isLeft) {
-        if (fadeIn) {
-            view.animate().cancel();
-            view.setAlpha(1f);
-            view.setVisibility(View.VISIBLE);
-
-            // A piecewise spring-like interpolation.
-            // End value in one animator call must match the start value in the next, otherwise
-            // there will be a discontinuity.
-            AnimatorSet anim = new AnimatorSet();
-            Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
-                    new PathInterpolator(0, 0.45f, .67f, 1f));
-            Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
-            Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
-                    secondInterpolator);
-            Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
-                    secondInterpolator);
-            Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
-                    secondInterpolator);
-            anim.play(first).before(second);
-            anim.play(second).before(third);
-            anim.play(third).before(fourth);
-            anim.start();
-        } else {
-            view.animate().cancel();
-            view.animate()
-                    .setInterpolator(new AccelerateInterpolator(1.5f))
-                    .setDuration(250)
-                    .alpha(0f);
-        }
-
-    }
-
-    /**
-     * Controls the visibility of the assist gesture handles.
-     *
-     * @param visible whether the handles should be shown
-     */
-    public void setAssistHintVisible(boolean visible) {
-        if (!mHandler.getLooper().isCurrentThread()) {
-            mHandler.post(() -> setAssistHintVisible(visible));
-            return;
-        }
-
-        if (mAssistHintBlocked && visible) {
-            if (VERBOSE) {
-                Log.v(TAG, "Assist hint blocked, cannot make it visible");
-            }
-            return;
-        }
-
-        if (mOverlay == null || mBottomOverlay == null) {
-            return;
-        }
-
-        if (mAssistHintVisible != visible) {
-            mAssistHintVisible = visible;
-
-            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
-            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
-            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
-                    R.id.assist_hint_left);
-            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
-                    R.id.assist_hint_right);
-
-            switch (mRotation) {
-                case RotationUtils.ROTATION_NONE:
-                    fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true);
-                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
-                    break;
-                case RotationUtils.ROTATION_LANDSCAPE:
-                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
-                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
-                    break;
-                case RotationUtils.ROTATION_SEASCAPE:
-                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
-                    fade(assistHintBottomLeft, mAssistHintVisible,  /* isLeft = */ true);
-                    break;
-                case RotationUtils.ROTATION_UPSIDE_DOWN:
-                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
-                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
-                    break;
-            }
-        }
-        updateWindowVisibilities();
-    }
-
-    /**
-     * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
-     */
-    public void setAssistHintBlocked(boolean blocked) {
-        if (!mHandler.getLooper().isCurrentThread()) {
-            mHandler.post(() -> setAssistHintBlocked(blocked));
-            return;
-        }
-
-        mAssistHintBlocked = blocked;
-        if (mAssistHintVisible && mAssistHintBlocked) {
-            hideAssistHandles();
-        }
     }
 
     @VisibleForTesting
@@ -316,15 +147,11 @@
         return thread.getThreadHandler();
     }
 
-    private boolean shouldHostHandles() {
-        return mInGesturalMode;
-    }
-
     private void startOnScreenDecorationsThread() {
         mRotation = RotationUtils.getExactRotation(mContext);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         updateRoundedCornerRadii();
-        if (hasRoundedCorners() || shouldDrawCutout() || shouldHostHandles()) {
+        if (hasRoundedCorners() || shouldDrawCutout()) {
             setupDecorations();
         }
 
@@ -501,26 +328,10 @@
             if (mOverlay != null) {
                 updateLayoutParams();
                 updateViews();
-                if (mAssistHintVisible) {
-                    // If assist handles are visible, hide them without animation and then make them
-                    // show once again (with corrected rotation).
-                    hideAssistHandles();
-                    setAssistHintVisible(true);
-                }
             }
         }
     }
 
-    private void hideAssistHandles() {
-        if (mOverlay != null && mBottomOverlay != null) {
-            mOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
-            mOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
-            mBottomOverlay.findViewById(R.id.assist_hint_left).setVisibility(View.GONE);
-            mBottomOverlay.findViewById(R.id.assist_hint_right).setVisibility(View.GONE);
-            mAssistHintVisible = false;
-        }
-    }
-
     private void updateRoundedCornerRadii() {
         final int newRoundedDefault = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.rounded_corner_radius);
@@ -569,52 +380,12 @@
             updateView(bottomRight, Gravity.TOP | Gravity.LEFT, 0);
         }
 
-        updateAssistantHandleViews();
         mCutoutTop.setRotation(mRotation);
         mCutoutBottom.setRotation(mRotation);
 
         updateWindowVisibilities();
     }
 
-    private void updateAssistantHandleViews() {
-        View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
-        View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
-        View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left);
-        View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right);
-
-        final int assistHintVisibility = mAssistHintVisible ? View.VISIBLE : View.INVISIBLE;
-
-        if (mRotation == RotationUtils.ROTATION_NONE) {
-            assistHintTopLeft.setVisibility(View.GONE);
-            assistHintTopRight.setVisibility(View.GONE);
-            assistHintBottomLeft.setVisibility(assistHintVisibility);
-            assistHintBottomRight.setVisibility(assistHintVisibility);
-            updateView(assistHintBottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
-            updateView(assistHintBottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
-        } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
-            assistHintTopLeft.setVisibility(View.GONE);
-            assistHintTopRight.setVisibility(assistHintVisibility);
-            assistHintBottomLeft.setVisibility(View.GONE);
-            assistHintBottomRight.setVisibility(assistHintVisibility);
-            updateView(assistHintTopRight, Gravity.BOTTOM | Gravity.LEFT, 270);
-            updateView(assistHintBottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
-        } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
-            assistHintTopLeft.setVisibility(assistHintVisibility);
-            assistHintTopRight.setVisibility(assistHintVisibility);
-            assistHintBottomLeft.setVisibility(View.GONE);
-            assistHintBottomRight.setVisibility(View.GONE);
-            updateView(assistHintTopLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
-            updateView(assistHintTopRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
-        } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
-            assistHintTopLeft.setVisibility(assistHintVisibility);
-            assistHintTopRight.setVisibility(View.GONE);
-            assistHintBottomLeft.setVisibility(assistHintVisibility);
-            assistHintBottomRight.setVisibility(View.GONE);
-            updateView(assistHintTopLeft, Gravity.BOTTOM | Gravity.RIGHT, 180);
-            updateView(assistHintBottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
-        }
-    }
-
     private void updateView(View v, int gravity, int rotation) {
         ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity;
         v.setRotation(rotation);
@@ -629,10 +400,7 @@
         boolean visibleForCutout = shouldDrawCutout()
                 && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE;
         boolean visibleForRoundedCorners = hasRoundedCorners();
-        boolean visibleForHandles = overlay.findViewById(R.id.assist_hint_left).getVisibility()
-                == View.VISIBLE || overlay.findViewById(R.id.assist_hint_right).getVisibility()
-                == View.VISIBLE;
-        overlay.setVisibility(visibleForCutout || visibleForRoundedCorners || visibleForHandles
+        overlay.setVisibility(visibleForCutout || visibleForRoundedCorners
                 ? View.VISIBLE : View.GONE);
     }
 
@@ -766,31 +534,6 @@
         view.setLayoutParams(params);
     }
 
-    @Override
-    public void onDarkIntensity(float darkIntensity) {
-        if (!mHandler.getLooper().isCurrentThread()) {
-            mHandler.post(() -> onDarkIntensity(darkIntensity));
-            return;
-        }
-        if (mOverlay != null) {
-            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
-            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
-
-            assistHintTopLeft.updateDarkness(darkIntensity);
-            assistHintTopRight.updateDarkness(darkIntensity);
-        }
-
-        if (mBottomOverlay != null) {
-            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
-                    R.id.assist_hint_left);
-            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
-                    R.id.assist_hint_right);
-
-            assistHintBottomLeft.updateDarkness(darkIntensity);
-            assistHintBottomRight.updateDarkness(darkIntensity);
-        }
-    }
-
     @VisibleForTesting
     static class TunablePaddingTagListener implements FragmentListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
index 6282c6e..e761a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui;
 
+import android.app.Service;
+
 import com.android.systemui.doze.DozeService;
 
 import dagger.Binds;
@@ -24,18 +26,12 @@
 import dagger.multibindings.IntoMap;
 
 /**
- * Services and Activities that are injectable should go here.
+ * Services that are injectable should go here.
  */
 @Module
 public abstract class ServiceBinder {
-
-    @Binds
-    public abstract ContextComponentHelper bindComponentHelper(
-            ContextComponentResolver componentHelper);
-
     @Binds
     @IntoMap
     @ClassKey(DozeService.class)
-    public abstract Object bindDozeService(DozeService service);
-
+    public abstract Service bindDozeService(DozeService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 78a1246..30fbef6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -24,7 +24,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Map;
-import java.util.function.Function;
 
 public abstract class SystemUI implements SysUiServiceProvider {
     public Context mContext;
@@ -62,7 +61,4 @@
 
         n.addExtras(extras);
     }
-
-    public interface Injector extends Function<Context, SystemUI> {
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 00ae992..2c8324c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -16,19 +16,31 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.Application;
 import android.app.Service;
+import android.content.ContentProvider;
+import android.content.Context;
 import android.content.Intent;
 
-import androidx.core.app.CoreComponentFactory;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.AppComponentFactory;
 
 import javax.inject.Inject;
 
 /**
  * Implementation of AppComponentFactory that injects into constructors.
+ *
+ * This class sets up dependency injection when creating our application.
+ *
+ * Services support dependency injection into their constructors.
+ *
+ * ContentProviders support injection into member variables - _not_ constructors.
  */
-public class SystemUIAppComponentFactory extends CoreComponentFactory {
+public class SystemUIAppComponentFactory extends AppComponentFactory {
 
+    private static final String TAG = "AppComponentFactory";
     @Inject
     public ContextComponentHelper mComponentHelper;
 
@@ -36,12 +48,14 @@
         super();
     }
 
+    @NonNull
     @Override
-    public Application instantiateApplication(ClassLoader cl, String className)
+    public Application instantiateApplicationCompat(
+            @NonNull ClassLoader cl, @NonNull String className)
             throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-        Application app = super.instantiateApplication(cl, className);
-        if (app instanceof SystemUIApplication) {
-            ((SystemUIApplication) app).setContextAvailableCallback(
+        Application app = super.instantiateApplicationCompat(cl, className);
+        if (app instanceof ContextInitializer) {
+            ((ContextInitializer) app).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
                         SystemUIFactory.getInstance().getRootComponent().inject(
@@ -53,24 +67,67 @@
         return app;
     }
 
+    @NonNull
     @Override
-    public Service instantiateService(ClassLoader cl, String className, Intent intent)
+    public ContentProvider instantiateProviderCompat(
+            @NonNull ClassLoader cl, @NonNull String className)
             throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-        Service service = mComponentHelper.resolve(className);
-        if (service != null) {
-            return checkCompatWrapper(service);
+
+        ContentProvider contentProvider = super.instantiateProviderCompat(cl, className);
+        if (contentProvider instanceof ContextInitializer) {
+            ((ContextInitializer) contentProvider).setContextAvailableCallback(
+                    context -> {
+                        SystemUIFactory.createFromConfig(context);
+                        SystemUIFactory.getInstance().getRootComponent().inject(
+                                contentProvider);
+                    }
+            );
         }
-        return super.instantiateService(cl, className, intent);
+
+        return contentProvider;
     }
 
-    static <T> T checkCompatWrapper(T obj) {
-        if (obj instanceof CompatWrapped) {
-            T wrapper = (T) ((CompatWrapped) obj).getWrapper();
-            if (wrapper != null) {
-                return wrapper;
-            }
+    @NonNull
+    @Override
+    public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
+            @Nullable Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        Activity activity = mComponentHelper.resolveActivity(className);
+        if (activity != null) {
+            return activity;
         }
+        return super.instantiateActivityCompat(cl, className, intent);
+    }
 
-        return obj;
+    @NonNull
+    @Override
+    public Service instantiateServiceCompat(
+            @NonNull ClassLoader cl, @NonNull String className, Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        if (mComponentHelper == null) {
+            // This shouldn't happen, but does when a device is freshly formatted.
+            // Bug filed against framework to take a look: http://b/141008541
+            SystemUIFactory.getInstance().getRootComponent().inject(
+                    SystemUIAppComponentFactory.this);
+        }
+        Service service = mComponentHelper.resolveService(className);
+        if (service != null) {
+            return service;
+        }
+        return super.instantiateServiceCompat(cl, className, intent);
+    }
+
+    /**
+     * A callback that receives a Context when one is ready.
+     */
+    public interface ContextAvailableCallback {
+        void onContextAvailable(Context context);
+    }
+
+    /**
+     * Implemented in classes that get started by the system before a context is available.
+     */
+    public interface ContextInitializer {
+        void setContextAvailableCallback(ContextAvailableCallback callback);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0b1fe69..56b5d08 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -48,11 +48,14 @@
 /**
  * Application class for SystemUI.
  */
-public class SystemUIApplication extends Application implements SysUiServiceProvider {
+public class SystemUIApplication extends Application implements SysUiServiceProvider,
+        SystemUIAppComponentFactory.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
+    private ContextComponentHelper mComponentHelper;
+
     /**
      * Hold a reference on the stuff we start.
      */
@@ -60,14 +63,26 @@
     private boolean mServicesStarted;
     private boolean mBootCompleted;
     private final Map<Class<?>, Object> mComponents = new HashMap<>();
-    private ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+
+    public SystemUIApplication() {
+        super();
+        Log.v(TAG, "SystemUIApplication constructed.");
+    }
 
     @Override
     public void onCreate() {
         super.onCreate();
+        Log.v(TAG, "SystemUIApplication created.");
         // This line is used to setup Dagger's dependency injection and should be kept at the
         // top of this method.
+        TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
+                Trace.TRACE_TAG_APP);
+        log.traceBegin("DependencyInjection");
         mContextAvailableCallback.onContextAvailable(this);
+        mComponentHelper = SystemUIFactory
+                .getInstance().getRootComponent().getContextComponentHelper();
+        log.traceEnd();
 
         // Set the application theme that is inherited by all services. Note that setting the
         // application theme in the manifest does only work for activities. Keep this in sync with
@@ -137,7 +152,7 @@
 
     /**
      * Ensures that all the Secondary user SystemUI services are running. If they are already
-     * running, this is a no-op. This is needed to conditinally start all the services, as we only
+     * running, this is a no-op. This is needed to conditionally start all the services, as we only
      * need to have it in the main process.
      * <p>This method must only be called from the main thread.</p>
      */
@@ -158,7 +173,9 @@
             // see ActivityManagerService.finishBooting()
             if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
                 mBootCompleted = true;
-                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
+                if (DEBUG) {
+                    Log.v(TAG, "BOOT_COMPLETED was already sent");
+                }
             }
         }
 
@@ -173,15 +190,13 @@
             if (DEBUG) Log.d(TAG, "loading: " + clsName);
             log.traceBegin("StartServices" + clsName);
             long ti = System.currentTimeMillis();
-            Class cls;
             try {
-                cls = Class.forName(clsName);
-                Object o = cls.newInstance();
-                if (o instanceof SystemUI.Injector) {
-                    o = ((SystemUI.Injector) o).apply(this);
+                SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
+                if (obj == null) {
+                    obj = (SystemUI) Class.forName(clsName).newInstance();
                 }
-                mServices[i] = (SystemUI) o;
-            } catch(ClassNotFoundException ex){
+                mServices[i] = obj;
+            } catch (ClassNotFoundException ex) {
                 throw new RuntimeException(ex);
             } catch (IllegalAccessException ex) {
                 throw new RuntimeException(ex);
@@ -198,7 +213,7 @@
             // Warn if initialization of component takes too long
             ti = System.currentTimeMillis() - ti;
             if (ti > 1000) {
-                Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
+                Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
             }
             if (mBootCompleted) {
                 mServices[i].onBootCompleted();
@@ -272,6 +287,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
+            Dependency.staticOnConfigurationChanged(newConfig);
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
@@ -290,11 +306,10 @@
         return mServices;
     }
 
-    void setContextAvailableCallback(ContextAvailableCallback callback) {
+    @Override
+    public void setContextAvailableCallback(
+            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
         mContextAvailableCallback = callback;
     }
 
-    interface ContextAvailableCallback {
-        void onContextAvailable(Context context);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
new file mode 100644
index 0000000..4531c89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import com.android.systemui.keyguard.KeyguardViewMediator;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * SystemUI objects that are injectable should go here.
+ */
+@Module
+public abstract class SystemUIBinder {
+    /** Inject into KeyguardViewMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewMediator.class)
+    public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index b67d7df..530dcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -24,13 +24,13 @@
 import android.util.Log;
 import android.view.ViewGroup;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -47,8 +47,7 @@
 import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.volume.VolumeDialogComponent;
 
 import java.util.function.Consumer;
@@ -70,6 +69,10 @@
     }
 
     public static void createFromConfig(Context context) {
+        if (mFactory != null) {
+            return;
+        }
+
         final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
         if (clsName == null || clsName.length() == 0) {
             throw new RuntimeException("No SystemUIFactory component configured");
@@ -86,13 +89,22 @@
         }
     }
 
+    @VisibleForTesting
+    static void cleanup() {
+        mFactory = null;
+    }
+
     public SystemUIFactory() {}
 
     private void init(Context context) {
-        initWithRootComponent(buildSystemUIRootComponent(context));
+        mRootComponent = buildSystemUIRootComponent(context);
+
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
-        Dependency.initDependencies(context);
+
+        Dependency dependency = new Dependency();
+        mRootComponent.createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 
     protected void initWithRootComponent(@NonNull SystemUIRootComponent rootComponent) {
@@ -120,24 +132,26 @@
     }
 
     public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
-            LockPatternUtils lockPatternUtils,  ViewGroup container,
+            LockPatternUtils lockPatternUtils, ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry,
             KeyguardBouncer.BouncerExpansionCallback expansionCallback,
-            FalsingManager falsingManager, KeyguardBypassController bypassController) {
+            KeyguardStateController keyguardStateController, FalsingManager falsingManager,
+            KeyguardBypassController bypassController) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, container,
                 dismissCallbackRegistry, falsingManager,
-                expansionCallback, UnlockMethodCache.getInstance(context),
-                KeyguardUpdateMonitor.getInstance(context), bypassController,
+                expansionCallback, keyguardStateController,
+                Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
 
     public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+            ScrimView scrimForBubble,
             LockscreenWallpaper lockscreenWallpaper,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
-            AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
-        return new ScrimController(scrimBehind, scrimInFront, scrimStateListener,
-                scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
+            AlarmManager alarmManager, KeyguardStateController keyguardStateController) {
+        return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+                scrimVisibleListener, dozeParameters, alarmManager, keyguardStateController);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
index 5d103eb..b0316e22 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
@@ -20,10 +20,11 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+import com.android.systemui.assist.AssistModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import javax.inject.Singleton;
 
@@ -34,7 +35,7 @@
  * A dagger module for injecting components of System UI that are not overridden by the System UI
  * implementation.
  */
-@Module
+@Module(includes = {AssistModule.class, ComponentBinder.class})
 public abstract class SystemUIModule {
 
     @Singleton
@@ -46,7 +47,7 @@
         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
             return null;
         }
-        return new KeyguardLiftController(context, statusBarStateController, asyncSensorManager);
+        return new KeyguardLiftController(statusBarStateController, asyncSensorManager);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
index f18c8b2..c70b2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
@@ -18,6 +18,8 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 
+import android.content.ContentProvider;
+
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.InjectionInflationController;
@@ -35,11 +37,17 @@
 @Component(modules = {
         DependencyProvider.class,
         DependencyBinder.class,
-        ServiceBinder.class,
         SystemUIFactory.ContextHolder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
 public interface SystemUIRootComponent {
+
+    /**
+     * Creates a GarbageMonitor.
+     */
+    @Singleton
+    ContextComponentHelper getContextComponentHelper();
+
     /**
      * Main dependency providing module.
      */
@@ -64,7 +72,7 @@
     InjectionInflationController.ViewCreator createViewCreator();
 
     /**
-     * Creatse a GarbageMonitor.
+     * Creates a GarbageMonitor.
      */
     @Singleton
     GarbageMonitor createGarbageMonitor();
@@ -76,7 +84,12 @@
     boolean allowNotificationLongPressName();
 
     /**
-     * Injects into the supplied argument.
+     * Member injection into the supplied argument.
      */
     void inject(SystemUIAppComponentFactory factory);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(ContentProvider contentProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index c8a2e17..2d2d91d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -38,25 +38,8 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        SystemUI[] services = ((SystemUIApplication) getApplication()).getServices();
-        if (args == null || args.length == 0) {
-            for (SystemUI ui: services) {
-                if (ui != null) {
-                    pw.println("dumping service: " + ui.getClass().getName());
-                    ui.dump(fd, pw, args);
-                }
-            }
-        } else {
-            String svc = args[0];
-            for (SystemUI ui: services) {
-                if (ui != null) {
-                    String name = ui.getClass().getName();
-                    if (name.endsWith(svc)) {
-                        ui.dump(fd, pw, args);
-                    }
-                }
-            }
-        }
+        SystemUIService.dumpServices(
+                ((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index dc1218d..8f1fcae 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -66,8 +66,14 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        SystemUI[] services = ((SystemUIApplication) getApplication()).getServices();
+        dumpServices(((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
+    }
+
+    static void dumpServices(
+            SystemUI[] services, FileDescriptor fd, PrintWriter pw, String[] args) {
         if (args == null || args.length == 0) {
+            pw.println("dumping service: " + Dependency.class.getName());
+            Dependency.staticDump(fd, pw, args);
             for (SystemUI ui: services) {
                 pw.println("dumping service: " + ui.getClass().getName());
                 ui.dump(fd, pw, args);
@@ -78,6 +84,9 @@
             }
         } else {
             String svc = args[0].toLowerCase();
+            if (Dependency.class.getName().toLowerCase().endsWith(svc)) {
+                Dependency.staticDump(fd, pw, args);
+            }
             for (SystemUI ui: services) {
                 String name = ui.getClass().getName().toLowerCase();
                 if (name.endsWith(svc)) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 0cee030..a3b94fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.assist;
 
+import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
@@ -28,20 +30,20 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
-import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.phone.NavigationModeController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.EnumMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
 
 /**
  * A class for managing Assistant handle logic.
@@ -49,6 +51,7 @@
  * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
  * {@link AssistHandleBehavior}.
  */
+@Singleton
 public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
 
     private static final String TAG = "AssistHandleBehavior";
@@ -67,10 +70,9 @@
     private final Handler mHandler;
     private final Runnable mHideHandles = this::hideHandles;
     private final Runnable mShowAndGo = this::showAndGoInternal;
-    private final Supplier<ScreenDecorations> mScreenDecorationsSupplier;
+    private final Provider<AssistHandleViewController> mAssistHandleViewController;
     private final PhenotypeHelper mPhenotypeHelper;
-    private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap =
-            new EnumMap<>(AssistHandleBehavior.class);
+    private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap;
 
     private boolean mHandlesShowing = false;
     private long mHandlesLastHiddenAt;
@@ -82,41 +84,25 @@
     private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
     private boolean mInGesturalMode;
 
-    AssistHandleBehaviorController(Context context, AssistUtils assistUtils, Handler handler) {
-        this(
-                context,
-                assistUtils,
-                handler,
-                () -> SysUiServiceProvider.getComponent(context, ScreenDecorations.class),
-                new PhenotypeHelper(),
-                /* testBehavior = */ null);
-    }
-
-    @VisibleForTesting
+    @Inject
     AssistHandleBehaviorController(
             Context context,
             AssistUtils assistUtils,
-            Handler handler,
-            Supplier<ScreenDecorations> screenDecorationsSupplier,
+            @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
+            Provider<AssistHandleViewController> assistHandleViewController,
             PhenotypeHelper phenotypeHelper,
-            @Nullable BehaviorController testBehavior) {
+            Map<AssistHandleBehavior, BehaviorController> behaviorMap,
+            NavigationModeController navigationModeController,
+            DumpController dumpController) {
         mContext = context;
         mAssistUtils = assistUtils;
         mHandler = handler;
-        mScreenDecorationsSupplier = screenDecorationsSupplier;
+        mAssistHandleViewController = assistHandleViewController;
         mPhenotypeHelper = phenotypeHelper;
-        mBehaviorMap.put(AssistHandleBehavior.OFF, new AssistHandleOffBehavior());
-        mBehaviorMap.put(AssistHandleBehavior.LIKE_HOME, new AssistHandleLikeHomeBehavior());
-        mBehaviorMap.put(
-                AssistHandleBehavior.REMINDER_EXP,
-                new AssistHandleReminderExpBehavior(handler, phenotypeHelper));
-        if (testBehavior != null) {
-            mBehaviorMap.put(AssistHandleBehavior.TEST, testBehavior);
-        }
+        mBehaviorMap = behaviorMap;
 
         mInGesturalMode = QuickStepContract.isGesturalMode(
-                Dependency.get(NavigationModeController.class)
-                        .addListener(this::handleNavigationModeChange));
+                navigationModeController.addListener(this::handleNavigationModeChange));
 
         setBehavior(getBehaviorMode());
         mPhenotypeHelper.addOnPropertiesChangedListener(
@@ -128,7 +114,8 @@
                                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
                     }
                 });
-        Dependency.get(DumpController.class).registerDumpable(TAG, this);
+
+        dumpController.registerDumpable(TAG, this);
     }
 
     @Override // AssistHandleCallbacks
@@ -177,6 +164,12 @@
         mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
     }
 
+    void onAssistHandlesRequested() {
+        if (mInGesturalMode) {
+            mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested();
+        }
+    }
+
     void setBehavior(AssistHandleBehavior behavior) {
         if (mCurrentBehavior == behavior) {
             return;
@@ -235,12 +228,13 @@
         }
 
         if (handlesUnblocked(ignoreThreshold)) {
-            ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
-            if (screenDecorations == null) {
-                Log.w(TAG, "Couldn't show handles, ScreenDecorations unavailable");
+            mHandlesShowing = true;
+            AssistHandleViewController assistHandleViewController =
+                    mAssistHandleViewController.get();
+            if (assistHandleViewController == null) {
+                Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
             } else {
-                mHandlesShowing = true;
-                screenDecorations.setAssistHintVisible(true);
+                assistHandleViewController.setAssistHintVisible(true);
             }
         }
     }
@@ -250,13 +244,14 @@
             return;
         }
 
-        ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
-        if (screenDecorations == null) {
-            Log.w(TAG, "Couldn't hide handles, ScreenDecorations unavailable");
+        mHandlesShowing = false;
+        mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
+        AssistHandleViewController assistHandleViewController =
+                mAssistHandleViewController.get();
+        if (assistHandleViewController == null) {
+            Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
         } else {
-            mHandlesShowing = false;
-            mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
-            screenDecorations.setAssistHintVisible(false);
+            assistHandleViewController.setAssistHintVisible(false);
         }
     }
 
@@ -316,6 +311,7 @@
         void onModeActivated(Context context, AssistHandleCallbacks callbacks);
         default void onModeDeactivated() {}
         default void onAssistantGesturePerformed() {}
+        default void onAssistHandlesRequested() {}
         default void dump(PrintWriter pw, String prefix) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
index 763315d..ccca447 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
@@ -20,18 +20,24 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
 /**
  * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
  * shown/hidden, respectively.
  */
+@Singleton
 final class AssistHandleLikeHomeBehavior implements BehaviorController {
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
@@ -41,35 +47,70 @@
                     handleDozingChanged(isDozing);
                 }
             };
+    private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ true);
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+            };
 
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
             this::handleSystemUiStateChange;
-    private final StatusBarStateController mStatusBarStateController;
-    private final SysUiState mSysUiFlagContainer;
+
+    private final Lazy<StatusBarStateController> mStatusBarStateController;
+    private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
+    private final Lazy<SysUiState> mSysUiFlagContainer;
 
     private boolean mIsDozing;
+    private boolean mIsAwake;
     private boolean mIsHomeHandleHiding;
 
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
 
-    AssistHandleLikeHomeBehavior() {
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
-        mSysUiFlagContainer = Dependency.get(SysUiState.class);
+    @Inject
+    AssistHandleLikeHomeBehavior(
+            Lazy<StatusBarStateController> statusBarStateController,
+            Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
+            Lazy<SysUiState> sysUiFlagContainer) {
+        mStatusBarStateController = statusBarStateController;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mSysUiFlagContainer = sysUiFlagContainer;
     }
 
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         mAssistHandleCallbacks = callbacks;
-        mIsDozing = mStatusBarStateController.isDozing();
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mSysUiFlagContainer.addCallback(mSysUiStateCallback);
+        mIsDozing = mStatusBarStateController.get().isDozing();
+        mStatusBarStateController.get().addCallback(mStatusBarStateListener);
+        mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
+                == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+        mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
+        mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
         callbackForCurrentState();
     }
 
     @Override
     public void onModeDeactivated() {
         mAssistHandleCallbacks = null;
-        mSysUiFlagContainer.removeCallback(mSysUiStateCallback);
+        mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
+        mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
+        mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
     }
 
     private static boolean isHomeHandleHiding(int sysuiStateFlags) {
@@ -85,6 +126,15 @@
         callbackForCurrentState();
     }
 
+    private void handleWakefullnessChanged(boolean isAwake) {
+        if (mIsAwake == isAwake) {
+            return;
+        }
+
+        mIsAwake = isAwake;
+        callbackForCurrentState();
+    }
+
     private void handleSystemUiStateChange(int sysuiStateFlags) {
         boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags);
         if (mIsHomeHandleHiding == isHomeHandleHiding) {
@@ -100,18 +150,23 @@
             return;
         }
 
-        if (mIsHomeHandleHiding || mIsDozing) {
+        if (mIsHomeHandleHiding || !isFullyAwake()) {
             mAssistHandleCallbacks.hide();
         } else {
             mAssistHandleCallbacks.showAndStay();
         }
     }
 
+    private boolean isFullyAwake() {
+        return mIsAwake && !mIsDozing;
+    }
+
     @Override
     public void dump(PrintWriter pw, String prefix) {
-        pw.println("Current AssistHandleLikeHomeBehavior State:");
+        pw.println(prefix + "Current AssistHandleLikeHomeBehavior State:");
 
         pw.println(prefix + "   mIsDozing=" + mIsDozing);
+        pw.println(prefix + "   mIsAwake=" + mIsAwake);
         pw.println(prefix + "   mIsHomeHandleHiding=" + mIsHomeHandleHiding);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
index f4130ae..df913f9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
@@ -20,9 +20,17 @@
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /** Assistant handle behavior that hides the Assistant handles. */
+@Singleton
 final class AssistHandleOffBehavior implements BehaviorController {
 
+    @Inject
+    AssistHandleOffBehavior() {
+    }
+
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         callbacks.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 7a9feb7..3ff1b97 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.assist;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
+import static com.android.systemui.assist.AssistModule.UPTIME_NAME;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
@@ -26,14 +27,14 @@
 import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.provider.Settings;
 
 import androidx.annotation.Nullable;
+import androidx.slice.Clock;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
@@ -49,11 +50,18 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
 /**
  * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode,
  * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or
  * entering overview.
  */
+@Singleton
 final class AssistHandleReminderExpBehavior implements BehaviorController {
 
     private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
@@ -111,9 +119,30 @@
                     handleOverviewShown();
                 }
             };
-
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
             this::handleSystemUiStateChanged;
+    private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ true);
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+            };
     private final BroadcastReceiver mDefaultHomeBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -123,15 +152,19 @@
     private final IntentFilter mDefaultHomeIntentFilter;
     private final Runnable mResetConsecutiveTaskSwitches = this::resetConsecutiveTaskSwitches;
 
+    private final Clock mClock;
     private final Handler mHandler;
     private final PhenotypeHelper mPhenotypeHelper;
-    private final StatusBarStateController mStatusBarStateController;
-    private final ActivityManagerWrapper mActivityManagerWrapper;
-    private final OverviewProxyService mOverviewProxyService;
-    private final SysUiState mSysUiFlagContainer;
+    private final Lazy<StatusBarStateController> mStatusBarStateController;
+    private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
+    private final Lazy<OverviewProxyService> mOverviewProxyService;
+    private final Lazy<SysUiState> mSysUiFlagContainer;
+    private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
+    private final Lazy<PackageManagerWrapper> mPackageManagerWrapper;
 
     private boolean mOnLockscreen;
     private boolean mIsDozing;
+    private boolean mIsAwake;
     private int mRunningTaskId;
     private boolean mIsNavBarHidden;
     private boolean mIsLauncherShowing;
@@ -150,13 +183,26 @@
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
     @Nullable private ComponentName mDefaultHome;
 
-    AssistHandleReminderExpBehavior(Handler handler, PhenotypeHelper phenotypeHelper) {
+    @Inject
+    AssistHandleReminderExpBehavior(
+            @Named(UPTIME_NAME) Clock clock,
+            @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
+            PhenotypeHelper phenotypeHelper,
+            Lazy<StatusBarStateController> statusBarStateController,
+            Lazy<ActivityManagerWrapper> activityManagerWrapper,
+            Lazy<OverviewProxyService> overviewProxyService,
+            Lazy<SysUiState> sysUiFlagContainer,
+            Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
+            Lazy<PackageManagerWrapper> packageManagerWrapper) {
+        mClock = clock;
         mHandler = handler;
         mPhenotypeHelper = phenotypeHelper;
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
-        mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
-        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mSysUiFlagContainer = Dependency.get(SysUiState.class);
+        mStatusBarStateController = statusBarStateController;
+        mActivityManagerWrapper = activityManagerWrapper;
+        mOverviewProxyService = overviewProxyService;
+        mSysUiFlagContainer = sysUiFlagContainer;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPackageManagerWrapper = packageManagerWrapper;
         mDefaultHomeIntentFilter = new IntentFilter();
         for (String action : DEFAULT_HOME_CHANGE_ACTIONS) {
             mDefaultHomeIntentFilter.addAction(action);
@@ -170,14 +216,18 @@
         mConsecutiveTaskSwitches = 0;
         mDefaultHome = getCurrentDefaultHome();
         context.registerReceiver(mDefaultHomeBroadcastReceiver, mDefaultHomeIntentFilter);
-        mOnLockscreen = onLockscreen(mStatusBarStateController.getState());
-        mIsDozing = mStatusBarStateController.isDozing();
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        ActivityManager.RunningTaskInfo runningTaskInfo = mActivityManagerWrapper.getRunningTask();
+        mOnLockscreen = onLockscreen(mStatusBarStateController.get().getState());
+        mIsDozing = mStatusBarStateController.get().isDozing();
+        mStatusBarStateController.get().addCallback(mStatusBarStateListener);
+        ActivityManager.RunningTaskInfo runningTaskInfo =
+                mActivityManagerWrapper.get().getRunningTask();
         mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
-        mActivityManagerWrapper.registerTaskStackListener(mTaskStackChangeListener);
-        mOverviewProxyService.addCallback(mOverviewProxyListener);
-        mSysUiFlagContainer.addCallback(mSysUiStateCallback);
+        mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener);
+        mOverviewProxyService.get().addCallback(mOverviewProxyListener);
+        mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
+        mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
+                == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+        mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
 
         mLearningTimeElapsed = Settings.Secure.getLong(
                 context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0);
@@ -185,7 +235,7 @@
                 context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0);
         mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
                 context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
-        mLastLearningTimestamp = SystemClock.uptimeMillis();
+        mLastLearningTimestamp = mClock.currentTimeMillis();
 
         callbackForCurrentState(/* justUnlocked = */ false);
     }
@@ -200,10 +250,11 @@
             Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
             mContext = null;
         }
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener);
-        mOverviewProxyService.removeCallback(mOverviewProxyListener);
-        mSysUiFlagContainer.removeCallback(mSysUiStateCallback);
+        mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
+        mActivityManagerWrapper.get().unregisterTaskStackListener(mTaskStackChangeListener);
+        mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
+        mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
+        mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
     }
 
     @Override
@@ -216,15 +267,20 @@
                 mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount);
     }
 
-    private static boolean isNavBarHidden(int sysuiStateFlags) {
-        return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+    @Override
+    public void onAssistHandlesRequested() {
+        if (mAssistHandleCallbacks != null
+                && isFullyAwake()
+                && !mIsNavBarHidden
+                && !mOnLockscreen) {
+            mAssistHandleCallbacks.showAndGo();
+        }
     }
 
     @Nullable
-    private static ComponentName getCurrentDefaultHome() {
+    private ComponentName getCurrentDefaultHome() {
         List<ResolveInfo> homeActivities = new ArrayList<>();
-        ComponentName defaultHome =
-                PackageManagerWrapper.getInstance().getHomeActivities(homeActivities);
+        ComponentName defaultHome = mPackageManagerWrapper.get().getHomeActivities(homeActivities);
         if (defaultHome != null) {
             return defaultHome;
         }
@@ -263,6 +319,16 @@
         callbackForCurrentState(/* justUnlocked = */ false);
     }
 
+    private void handleWakefullnessChanged(boolean isAwake) {
+        if (mIsAwake == isAwake) {
+            return;
+        }
+
+        resetConsecutiveTaskSwitches();
+        mIsAwake = isAwake;
+        callbackForCurrentState(/* justUnlocked = */ false);
+    }
+
     private void handleTaskStackTopChanged(int taskId, @Nullable ComponentName taskComponentName) {
         if (mRunningTaskId == taskId || taskComponentName == null) {
             return;
@@ -280,7 +346,8 @@
     }
 
     private void handleSystemUiStateChanged(int sysuiStateFlags) {
-        boolean isNavBarHidden = isNavBarHidden(sysuiStateFlags);
+        boolean isNavBarHidden =
+                (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
         if (mIsNavBarHidden == isNavBarHidden) {
             return;
         }
@@ -315,7 +382,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
+        if (!isFullyAwake() || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
             mAssistHandleCallbacks.hide();
         } else if (justUnlocked) {
             long currentEpochDay = LocalDate.now().toEpochDay();
@@ -337,7 +404,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || isSuppressed()) {
+        if (!isFullyAwake() || mIsNavBarHidden || isSuppressed()) {
             mAssistHandleCallbacks.hide();
         } else if (mOnLockscreen) {
             mAssistHandleCallbacks.showAndStay();
@@ -367,15 +434,15 @@
             return;
         }
 
-        long currentTimestamp = SystemClock.uptimeMillis();
+        long currentTimestamp = mClock.currentTimeMillis();
         mLearningTimeElapsed += currentTimestamp - mLastLearningTimestamp;
         mLastLearningTimestamp = currentTimestamp;
-        // TODO(b/140034473)
-        whitelistIpcs(() -> Settings.Secure.putLong(
-                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
 
         mIsLearned =
                 mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
+
+        mHandler.post(() -> Settings.Secure.putLong(
+                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
     }
 
     private void resetConsecutiveTaskSwitches() {
@@ -388,6 +455,10 @@
         mHandler.postDelayed(mResetConsecutiveTaskSwitches, getShowAndGoDelayResetTimeoutMs());
     }
 
+    private boolean isFullyAwake() {
+        return mIsAwake && !mIsDozing;
+    }
+
     private long getLearningTimeMs() {
         return mPhenotypeHelper.getLong(
                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS,
@@ -447,6 +518,7 @@
         pw.println(prefix + "Current AssistHandleReminderExpBehavior State:");
         pw.println(prefix + "   mOnLockscreen=" + mOnLockscreen);
         pw.println(prefix + "   mIsDozing=" + mIsDozing);
+        pw.println(prefix + "   mIsAwake=" + mIsAwake);
         pw.println(prefix + "   mRunningTaskId=" + mRunningTaskId);
         pw.println(prefix + "   mDefaultHome=" + mDefaultHome);
         pw.println(prefix + "   mIsNavBarHidden=" + mIsNavBarHidden);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
new file mode 100644
index 0000000..f9ffb80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
@@ -0,0 +1,172 @@
+/*
+ * 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.assist;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.os.Handler;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.CornerHandleView;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NavigationBarTransitions;
+
+/**
+ * A class for managing Assistant handle show, hide and animation.
+ */
+public class AssistHandleViewController implements NavigationBarTransitions.DarkIntensityListener {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "AssistHandleViewController";
+
+    private Handler mHandler;
+    private CornerHandleView mAssistHintLeft;
+    private CornerHandleView mAssistHintRight;
+
+    @VisibleForTesting
+    boolean mAssistHintVisible;
+    @VisibleForTesting
+    boolean mAssistHintBlocked = false;
+
+    public AssistHandleViewController(Handler handler, View navBar) {
+        mHandler = handler;
+        mAssistHintLeft = navBar.findViewById(R.id.assist_hint_left);
+        mAssistHintRight = navBar.findViewById(R.id.assist_hint_right);
+    }
+
+    @Override
+    public void onDarkIntensity(float darkIntensity) {
+        mAssistHintLeft.updateDarkness(darkIntensity);
+        mAssistHintRight.updateDarkness(darkIntensity);
+    }
+
+    /**
+     * Controls the visibility of the assist gesture handles.
+     *
+     * @param visible whether the handles should be shown
+     */
+    public void setAssistHintVisible(boolean visible) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.post(() -> setAssistHintVisible(visible));
+            return;
+        }
+
+        if (mAssistHintBlocked && visible) {
+            if (DEBUG) {
+                Log.v(TAG, "Assist hint blocked, cannot make it visible");
+            }
+            return;
+        }
+
+        if (mAssistHintVisible != visible) {
+            mAssistHintVisible = visible;
+            fade(mAssistHintLeft, mAssistHintVisible, /* isLeft = */ true);
+            fade(mAssistHintRight, mAssistHintVisible, /* isLeft = */ false);
+        }
+    }
+
+    /**
+     * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
+     */
+    public void setAssistHintBlocked(boolean blocked) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.post(() -> setAssistHintBlocked(blocked));
+            return;
+        }
+
+        mAssistHintBlocked = blocked;
+        if (mAssistHintVisible && mAssistHintBlocked) {
+            hideAssistHandles();
+        }
+    }
+
+    private void hideAssistHandles() {
+        mAssistHintLeft.setVisibility(View.GONE);
+        mAssistHintRight.setVisibility(View.GONE);
+        mAssistHintVisible = false;
+    }
+
+    /**
+     * Returns an animator that animates the given view from start to end over durationMs. Start and
+     * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
+     * overshoot.
+     */
+    Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
+            Interpolator interpolator) {
+        // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
+        float scaleStart = MathUtils.lerp(2f, 1f, start);
+        float scaleEnd = MathUtils.lerp(2f, 1f, end);
+        Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
+        Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
+        float translationStart = MathUtils.lerp(0.2f, 0f, start);
+        float translationEnd = MathUtils.lerp(0.2f, 0f, end);
+        int xDirection = isLeft ? -1 : 1;
+        Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+                xDirection * translationStart * view.getWidth(),
+                xDirection * translationEnd * view.getWidth());
+        Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
+                translationStart * view.getHeight(), translationEnd * view.getHeight());
+
+        AnimatorSet set = new AnimatorSet();
+        set.play(scaleX).with(scaleY);
+        set.play(scaleX).with(translateX);
+        set.play(scaleX).with(translateY);
+        set.setDuration(durationMs);
+        set.setInterpolator(interpolator);
+        return set;
+    }
+
+    private void fade(View view, boolean fadeIn, boolean isLeft) {
+        if (fadeIn) {
+            view.animate().cancel();
+            view.setAlpha(1f);
+            view.setVisibility(View.VISIBLE);
+
+            // A piecewise spring-like interpolation.
+            // End value in one animator call must match the start value in the next, otherwise
+            // there will be a discontinuity.
+            AnimatorSet anim = new AnimatorSet();
+            Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
+                    new PathInterpolator(0, 0.45f, .67f, 1f));
+            Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
+            Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
+                    secondInterpolator);
+            Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
+                    secondInterpolator);
+            Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
+                    secondInterpolator);
+            anim.play(first).before(second);
+            anim.play(second).before(third);
+            anim.play(third).before(fourth);
+            anim.start();
+        } else {
+            view.animate().cancel();
+            view.animate()
+                .setInterpolator(new AccelerateInterpolator(1.5f))
+                .setDuration(250)
+                .alpha(0f);
+        }
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index c4feac1..1f950f6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -103,6 +103,8 @@
     private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
     private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
     public static final String INVOCATION_TYPE_KEY = "invocation_type";
+    protected static final String ACTION_KEY = "action";
+    protected static final String SHOW_ASSIST_HANDLES_ACTION = "show_assist_handles";
 
     public static final int INVOCATION_TYPE_GESTURE = 1;
     public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2;
@@ -154,15 +156,18 @@
     };
 
     @Inject
-    public AssistManager(DeviceProvisionedController controller, Context context) {
+    public AssistManager(
+            DeviceProvisionedController controller,
+            Context context,
+            AssistUtils assistUtils,
+            AssistHandleBehaviorController handleController) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        mAssistUtils = new AssistUtils(context);
+        mAssistUtils = assistUtils;
         mAssistDisclosure = new AssistDisclosure(context, new Handler());
         mPhoneStateMonitor = new PhoneStateMonitor(context);
-        mHandleController =
-                new AssistHandleBehaviorController(context, mAssistUtils, new Handler());
+        mHandleController = handleController;
 
         registerVoiceInteractionSessionListener();
         mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
@@ -211,6 +216,9 @@
                         if (VERBOSE) {
                             Log.v(TAG, "UI hints received");
                         }
+                        if (SHOW_ASSIST_HANDLES_ACTION.equals(hints.getString(ACTION_KEY))) {
+                            requestAssistHandles();
+                        }
                     }
                 });
     }
@@ -284,6 +292,10 @@
         mUiController.onGestureCompletion(velocity);
     }
 
+    protected void requestAssistHandles() {
+        mHandleController.onAssistHandlesRequested();
+    }
+
     public void hideAssist() {
         mAssistUtils.hideCurrentSession();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
new file mode 100644
index 0000000..739eade
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -0,0 +1,88 @@
+/*
+ * 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.assist;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+
+import androidx.slice.Clock;
+
+import com.android.internal.app.AssistUtils;
+import com.android.systemui.statusbar.NavigationBarController;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Module for dagger injections related to the Assistant. */
+@Module
+public abstract class AssistModule {
+
+    static final String ASSIST_HANDLE_THREAD_NAME = "assist_handle_thread";
+    static final String UPTIME_NAME = "uptime";
+
+    @Provides
+    @Singleton
+    @Named(ASSIST_HANDLE_THREAD_NAME)
+    static Handler provideBackgroundHandler() {
+        final HandlerThread backgroundHandlerThread =
+                new HandlerThread("AssistHandleThread");
+        backgroundHandlerThread.start();
+        return backgroundHandlerThread.getThreadHandler();
+    }
+
+    @Provides
+    @Singleton
+    static Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController>
+            provideAssistHandleBehaviorControllerMap(
+                    AssistHandleOffBehavior offBehavior,
+                    AssistHandleLikeHomeBehavior likeHomeBehavior,
+                    AssistHandleReminderExpBehavior reminderExpBehavior) {
+        Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController> map =
+                new EnumMap<>(AssistHandleBehavior.class);
+        map.put(AssistHandleBehavior.OFF, offBehavior);
+        map.put(AssistHandleBehavior.LIKE_HOME, likeHomeBehavior);
+        map.put(AssistHandleBehavior.REMINDER_EXP, reminderExpBehavior);
+        return map;
+    }
+
+    @Provides
+    static AssistHandleViewController provideAssistHandleViewController(
+            NavigationBarController navigationBarController) {
+        return navigationBarController.getAssistHandlerViewController();
+    }
+
+    @Provides
+    @Singleton
+    static AssistUtils provideAssistUtils(Context context) {
+        return new AssistUtils(context);
+    }
+
+    @Provides
+    @Singleton
+    @Named(UPTIME_NAME)
+    static Clock provideSystemClock() {
+        return SystemClock::uptimeMillis;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java b/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java
index c21a717..cbb56e8 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java
@@ -24,8 +24,18 @@
 
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Wrapper class for retrieving phenotype flag values.
+ *
+ * Can be mocked in tests for ease of testing the effects of particular values.
+ */
+@Singleton
 public class PhenotypeHelper {
 
+    @Inject
     public PhenotypeHelper() {}
 
     public long getLong(String name, long defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 0c4f051..bf81e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.assist.ui;
 
 import static com.android.systemui.assist.AssistManager.DISMISS_REASON_INVOCATION_CANCELLED;
+import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_GESTURE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -24,6 +25,7 @@
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.metrics.LogMaker;
+import android.os.Build;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.Gravity;
@@ -36,9 +38,11 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.NavigationBarController;
+
+import java.util.Locale;
 
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
@@ -50,6 +54,9 @@
 
     private static final long ANIM_DURATION_MS = 200;
 
+    private static final boolean VERBOSE = Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
+            || Build.TYPE.toLowerCase(Locale.ROOT).equals("eng");
+
     protected final FrameLayout mRoot;
     protected InvocationLightsView mInvocationLightsView;
 
@@ -114,6 +121,7 @@
     @Override // AssistManager.UiController
     public void onGestureCompletion(float velocity) {
         animateInvocationCompletion(AssistManager.INVOCATION_TYPE_GESTURE, velocity);
+        logInvocationProgressMetrics(INVOCATION_TYPE_GESTURE, 1, mInvocationInProgress);
     }
 
     @Override // AssistManager.UiController
@@ -127,16 +135,27 @@
         updateAssistHandleVisibility();
     }
 
-    protected static void logInvocationProgressMetrics(
+    protected void logInvocationProgressMetrics(
             int type, float progress, boolean invocationWasInProgress) {
         // Logs assistant invocation start.
+        if (progress == 1f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation complete: type=" + type);
+            }
+        }
         if (!invocationWasInProgress && progress > 0.f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation started: type=" + type);
+            }
             MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT)
                     .setType(MetricsEvent.TYPE_ACTION)
                     .setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type)));
         }
         // Logs assistant invocation cancelled.
-        if (invocationWasInProgress && progress == 0f) {
+        if (!mInvocationAnimator.isRunning() && invocationWasInProgress && progress == 0f) {
+            if (VERBOSE) {
+                Log.v(TAG, "Invocation cancelled: type=" + type);
+            }
             MetricsLogger.action(new LogMaker(MetricsEvent.ASSISTANT)
                     .setType(MetricsEvent.TYPE_DISMISS)
                     .setSubtype(DISMISS_REASON_INVOCATION_CANCELLED));
@@ -144,9 +163,11 @@
     }
 
     private void updateAssistHandleVisibility() {
-        ScreenDecorations decorations = SysUiServiceProvider.getComponent(mRoot.getContext(),
-                ScreenDecorations.class);
-        decorations.setAssistHintBlocked(mInvocationInProgress);
+        AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
+                .getAssistHandlerViewController();
+        if (controller != null) {
+            controller.setAssistHintBlocked(mInvocationInProgress);
+        }
     }
 
     private void attach() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
new file mode 100644
index 0000000..1d47fc5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -0,0 +1,212 @@
+/*
+ * 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.biometrics;
+
+import android.content.Context;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+public class AuthBiometricFaceView extends AuthBiometricView {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
+
+    // Delay before dismissing after being authenticated/confirmed.
+    private static final int HIDE_DELAY_MS = 500;
+
+    public static class IconController extends Animatable2.AnimationCallback {
+        Context mContext;
+        ImageView mIconView;
+        TextView mTextView;
+        Handler mHandler;
+        boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
+        @BiometricState int mState;
+
+        IconController(Context context, ImageView iconView, TextView textView) {
+            mContext = context;
+            mIconView = iconView;
+            mTextView = textView;
+            mHandler = new Handler(Looper.getMainLooper());
+            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+        }
+
+        void animateOnce(int iconRes) {
+            animateIcon(iconRes, false);
+        }
+
+        public void showStaticDrawable(int iconRes) {
+            mIconView.setImageDrawable(mContext.getDrawable(iconRes));
+        }
+
+        void animateIcon(int iconRes, boolean repeat) {
+            final AnimatedVectorDrawable icon =
+                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
+            mIconView.setImageDrawable(icon);
+            icon.forceAnimationOnUI();
+            if (repeat) {
+                icon.registerAnimationCallback(this);
+            }
+            icon.start();
+        }
+
+        void startPulsing() {
+            mLastPulseLightToDark = false;
+            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
+        }
+
+        void pulseInNextDirection() {
+            int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
+                    : R.drawable.face_dialog_pulse_light_to_dark;
+            animateIcon(iconRes, true /* repeat */);
+            mLastPulseLightToDark = !mLastPulseLightToDark;
+        }
+
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            super.onAnimationEnd(drawable);
+            if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
+                pulseInNextDirection();
+            }
+        }
+
+        public void updateState(int lastState, int newState) {
+            final boolean lastStateIsErrorIcon =
+                    lastState == STATE_ERROR || lastState == STATE_HELP;
+
+            if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+                showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticating));
+            } else if (newState == STATE_AUTHENTICATING) {
+                startPulsing();
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticating));
+            } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_confirmed));
+            } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+                animateOnce(R.drawable.face_dialog_error_to_idle);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_idle));
+            } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
+                animateOnce(R.drawable.face_dialog_dark_to_error);
+            } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_PENDING_CONFIRMATION) {
+                animateOnce(R.drawable.face_dialog_wink_from_dark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_IDLE) {
+                showStaticDrawable(R.drawable.face_dialog_idle_static);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_idle));
+            } else {
+                Log.w(TAG, "Unhandled state: " + newState);
+            }
+            mState = newState;
+        }
+    }
+
+    @VisibleForTesting IconController mIconController;
+
+    public AuthBiometricFaceView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricFaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int getDelayAfterAuthenticatedDurationMs() {
+        return HIDE_DELAY_MS;
+    }
+
+    @Override
+    protected int getStateForAfterError() {
+        return STATE_IDLE;
+    }
+
+    @Override
+    protected void handleResetAfterError() {
+        resetErrorView(mContext, mIndicatorView);
+    }
+
+    @Override
+    protected void handleResetAfterHelp() {
+        resetErrorView(mContext, mIndicatorView);
+    }
+
+    @Override
+    protected boolean supportsSmallDialog() {
+        return true;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconController = new IconController(mContext, mIconView, mIndicatorView);
+    }
+
+    @Override
+    public void updateState(@BiometricState int newState) {
+        mIconController.updateState(mState, newState);
+
+        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+                (newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
+            resetErrorView(mContext, mIndicatorView);
+        }
+
+        // Do this last since the state variable gets updated.
+        super.updateState(newState);
+    }
+
+    @Override
+    public void onAuthenticationFailed(String failureReason) {
+        if (mSize == AuthDialog.SIZE_MEDIUM) {
+            mTryAgainButton.setVisibility(View.VISIBLE);
+            mPositiveButton.setVisibility(View.GONE);
+        }
+
+        // Do this last since wa want to know if the button is being animated (in the case of
+        // small -> medium dialog)
+        super.onAuthenticationFailed(failureReason);
+    }
+
+    static void resetErrorView(Context context, TextView textView) {
+        textView.setTextColor(context.getResources().getColor(
+                R.color.biometric_dialog_gray, context.getTheme()));
+        textView.setVisibility(View.INVISIBLE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
new file mode 100644
index 0000000..176e9e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
@@ -0,0 +1,147 @@
+/*
+ * 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.biometrics;
+
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public class AuthBiometricFingerprintView extends AuthBiometricView {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView";
+
+    public AuthBiometricFingerprintView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricFingerprintView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int getDelayAfterAuthenticatedDurationMs() {
+        return 0;
+    }
+
+    @Override
+    protected int getStateForAfterError() {
+        return STATE_AUTHENTICATING;
+    }
+
+    @Override
+    protected void handleResetAfterError() {
+        showTouchSensorString();
+    }
+
+    @Override
+    protected void handleResetAfterHelp() {
+        showTouchSensorString();
+    }
+
+    @Override
+    protected boolean supportsSmallDialog() {
+        return false;
+    }
+
+    @Override
+    public void updateState(@BiometricState int newState) {
+        updateIcon(mState, newState);
+
+        // Do this last since the state variable gets updated.
+        super.updateState(newState);
+    }
+
+    @Override
+    void onAttachedToWindowInternal() {
+        super.onAttachedToWindowInternal();
+        showTouchSensorString();
+    }
+
+    private void showTouchSensorString() {
+        mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
+        mIndicatorView.setTextColor(R.color.biometric_dialog_gray);
+    }
+
+    private void updateIcon(int lastState, int newState) {
+        final Drawable icon = getAnimationForTransition(lastState, newState);
+        if (icon == null) {
+            Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
+            return;
+        }
+
+        final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
+                ? (AnimatedVectorDrawable) icon
+                : null;
+
+        mIconView.setImageDrawable(icon);
+
+        if (animation != null && shouldAnimateForTransition(lastState, newState)) {
+            animation.forceAnimationOnUI();
+            animation.start();
+        }
+    }
+
+    private boolean shouldAnimateForTransition(int oldState, int newState) {
+        switch (newState) {
+            case STATE_HELP:
+            case STATE_ERROR:
+                return true;
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    return true;
+                } else {
+                    return false;
+                }
+            case STATE_AUTHENTICATED:
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    private Drawable getAnimationForTransition(int oldState, int newState) {
+        int iconRes;
+
+        switch (newState) {
+            case STATE_HELP:
+            case STATE_ERROR:
+                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                break;
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    iconRes = R.drawable.fingerprint_dialog_error_to_fp;
+                } else {
+                    iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                }
+                break;
+            case STATE_AUTHENTICATED:
+                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                break;
+            default:
+                return null;
+        }
+
+        return mContext.getDrawable(iconRes);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
new file mode 100644
index 0000000..73bbce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -0,0 +1,658 @@
+/*
+ * 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.biometrics;
+
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ */
+public abstract class AuthBiometricView extends LinearLayout {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricView";
+
+    /**
+     * Authentication hardware idle.
+     */
+    protected static final int STATE_IDLE = 0;
+    /**
+     * UI animating in, authentication hardware active.
+     */
+    protected static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
+    /**
+     * UI animated in, authentication hardware active.
+     */
+    protected static final int STATE_AUTHENTICATING = 2;
+    /**
+     * UI animated in, authentication hardware active.
+     */
+    protected static final int STATE_HELP = 3;
+    /**
+     * Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle.
+     */
+    protected static final int STATE_ERROR = 4;
+    /**
+     * Authenticated, waiting for user confirmation. Authentication hardware idle.
+     */
+    protected static final int STATE_PENDING_CONFIRMATION = 5;
+    /**
+     * Authenticated, dialog animating away soon.
+     */
+    protected static final int STATE_AUTHENTICATED = 6;
+    
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
+            STATE_ERROR, STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED})
+    @interface BiometricState {}
+
+    /**
+     * Callback to the parent when a user action has occurred.
+     */
+    interface Callback {
+        int ACTION_AUTHENTICATED = 1;
+        int ACTION_USER_CANCELED = 2;
+        int ACTION_BUTTON_NEGATIVE = 3;
+        int ACTION_BUTTON_TRY_AGAIN = 4;
+        int ACTION_ERROR = 5;
+
+        /**
+         * When an action has occurred. The caller will only invoke this when the callback should
+         * be propagated. e.g. the caller will handle any necessary delay.
+         * @param action
+         */
+        void onAction(int action);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        AuthBiometricView mBiometricView;
+
+        public Button getNegativeButton() {
+            return mBiometricView.findViewById(R.id.button_negative);
+        }
+
+        public Button getPositiveButton() {
+            return mBiometricView.findViewById(R.id.button_positive);
+        }
+
+        public Button getTryAgainButton() {
+            return mBiometricView.findViewById(R.id.button_try_again);
+        }
+
+        public TextView getTitleView() {
+            return mBiometricView.findViewById(R.id.title);
+        }
+
+        public TextView getSubtitleView() {
+            return mBiometricView.findViewById(R.id.subtitle);
+        }
+
+        public TextView getDescriptionView() {
+            return mBiometricView.findViewById(R.id.description);
+        }
+
+        public TextView getIndicatorView() {
+            return mBiometricView.findViewById(R.id.indicator);
+        }
+
+        public ImageView getIconView() {
+            return mBiometricView.findViewById(R.id.biometric_icon);
+        }
+
+        public int getDelayAfterError() {
+            return BiometricPrompt.HIDE_DIALOG_DELAY;
+        }
+    }
+
+    private final Injector mInjector;
+    private final Handler mHandler;
+    private final AccessibilityManager mAccessibilityManager;
+    private final int mTextColorError;
+    private final int mTextColorHint;
+
+    private AuthPanelController mPanelController;
+    private Bundle mBundle;
+    private boolean mRequireConfirmation;
+    @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
+
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private TextView mDescriptionView;
+    protected ImageView mIconView;
+    @VisibleForTesting protected TextView mIndicatorView;
+    @VisibleForTesting Button mNegativeButton;
+    @VisibleForTesting Button mPositiveButton;
+    @VisibleForTesting Button mTryAgainButton;
+
+    // Measurements when biometric view is showing text, buttons, etc.
+    private int mMediumHeight;
+    private int mMediumWidth;
+
+    private Callback mCallback;
+    protected @BiometricState int mState;
+
+    private float mIconOriginalY;
+
+    protected boolean mDialogSizeAnimating;
+    protected Bundle mSavedState;
+
+    /**
+     * Delay after authentication is confirmed, before the dialog should be animated away.
+     */
+    protected abstract int getDelayAfterAuthenticatedDurationMs();
+    /**
+     * State that the dialog/icon should be in after showing a help message.
+     */
+    protected abstract int getStateForAfterError();
+    /**
+     * Invoked when the error message is being cleared.
+     */
+    protected abstract void handleResetAfterError();
+    /**
+     * Invoked when the help message is being cleared.
+     */
+    protected abstract void handleResetAfterHelp();
+
+    /**
+     * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
+     */
+    protected abstract boolean supportsSmallDialog();
+
+    private final Runnable mResetErrorRunnable;
+
+    private final Runnable mResetHelpRunnable;
+
+    private final OnClickListener mBackgroundClickListener = (view) -> {
+        if (mState == STATE_AUTHENTICATED) {
+            Log.w(TAG, "Ignoring background click after authenticated");
+            return;
+        } else if (mSize == AuthDialog.SIZE_SMALL) {
+            Log.w(TAG, "Ignoring background click during small dialog");
+            return;
+        }
+        mCallback.onAction(Callback.ACTION_USER_CANCELED);
+    };
+
+    public AuthBiometricView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricView(Context context, AttributeSet attrs) {
+        this(context, attrs, new Injector());
+    }
+
+    @VisibleForTesting
+    AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
+        super(context, attrs);
+        mHandler = new Handler(Looper.getMainLooper());
+        mTextColorError = getResources().getColor(
+                R.color.biometric_dialog_error, context.getTheme());
+        mTextColorHint = getResources().getColor(
+                R.color.biometric_dialog_gray, context.getTheme());
+
+        mInjector = injector;
+        mInjector.mBiometricView = this;
+
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+
+        mResetErrorRunnable = () -> {
+            updateState(getStateForAfterError());
+            handleResetAfterError();
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        };
+
+        mResetHelpRunnable = () -> {
+            updateState(STATE_AUTHENTICATING);
+            handleResetAfterHelp();
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        };
+    }
+
+    public void setPanelController(AuthPanelController panelController) {
+        mPanelController = panelController;
+    }
+
+    public void setBiometricPromptBundle(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    public void setBackgroundView(View backgroundView) {
+        backgroundView.setOnClickListener(mBackgroundClickListener);
+    }
+
+    public void setRequireConfirmation(boolean requireConfirmation) {
+        mRequireConfirmation = requireConfirmation;
+    }
+
+    @VisibleForTesting
+    void updateSize(@AuthDialog.DialogSize int newSize) {
+        Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
+        if (newSize == AuthDialog.SIZE_SMALL) {
+            mTitleView.setVisibility(View.GONE);
+            mSubtitleView.setVisibility(View.GONE);
+            mDescriptionView.setVisibility(View.GONE);
+            mIndicatorView.setVisibility(View.GONE);
+            mNegativeButton.setVisibility(View.GONE);
+
+            final float iconPadding = getResources()
+                    .getDimension(R.dimen.biometric_dialog_icon_padding);
+            mIconView.setY(getHeight() - mIconView.getHeight() - iconPadding);
+
+            final int newHeight = mIconView.getHeight() + 2 * (int) iconPadding;
+            mPanelController.updateForContentDimensions(mMediumWidth, newHeight,
+                    false /* animate */);
+
+            mSize = newSize;
+        } else if (mSize == AuthDialog.SIZE_SMALL && newSize == AuthDialog.SIZE_MEDIUM) {
+            if (mDialogSizeAnimating) {
+                return;
+            }
+            mDialogSizeAnimating = true;
+
+            // Animate the icon back to original position
+            final ValueAnimator iconAnimator =
+                    ValueAnimator.ofFloat(mIconView.getY(), mIconOriginalY);
+            iconAnimator.addUpdateListener((animation) -> {
+                mIconView.setY((float) animation.getAnimatedValue());
+            });
+
+            // Animate the text
+            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+            opacityAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            opacityAnimator.addUpdateListener((animation) -> {
+                final float opacity = (float) animation.getAnimatedValue();
+
+                mTitleView.setAlpha(opacity);
+                mIndicatorView.setAlpha(opacity);
+                mNegativeButton.setAlpha(opacity);
+                mTryAgainButton.setAlpha(opacity);
+
+                if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+                    mSubtitleView.setAlpha(opacity);
+                }
+                if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+                    mDescriptionView.setAlpha(opacity);
+                }
+            });
+
+            // Choreograph together
+            final AnimatorSet as = new AnimatorSet();
+            as.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            as.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    super.onAnimationStart(animation);
+                    mTitleView.setVisibility(View.VISIBLE);
+                    mIndicatorView.setVisibility(View.VISIBLE);
+                    mNegativeButton.setVisibility(View.VISIBLE);
+                    mTryAgainButton.setVisibility(View.VISIBLE);
+
+                    if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+                        mSubtitleView.setVisibility(View.VISIBLE);
+                    }
+                    if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+                        mDescriptionView.setVisibility(View.VISIBLE);
+                    }
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    mSize = newSize;
+                    mDialogSizeAnimating = false;
+                    Utils.notifyAccessibilityContentChanged(mAccessibilityManager,
+                            AuthBiometricView.this);
+                }
+            });
+
+            as.play(iconAnimator).with(opacityAnimator);
+            as.start();
+            // Animate the panel
+            mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+                    true /* animate */);
+        } else if (newSize == AuthDialog.SIZE_MEDIUM) {
+            mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+                    false /* animate */);
+            mSize = newSize;
+        } else {
+            Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
+        }
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    public void updateState(@BiometricState int newState) {
+        Log.v(TAG, "newState: " + newState);
+
+        switch (newState) {
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                removePendingAnimations();
+                if (mRequireConfirmation) {
+                    mPositiveButton.setEnabled(false);
+                    mPositiveButton.setVisibility(View.VISIBLE);
+                }
+                break;
+
+            case STATE_AUTHENTICATED:
+                if (mSize != AuthDialog.SIZE_SMALL) {
+                    mPositiveButton.setVisibility(View.GONE);
+                    mNegativeButton.setVisibility(View.GONE);
+                    mIndicatorView.setVisibility(View.INVISIBLE);
+                }
+                announceForAccessibility(getResources()
+                        .getString(R.string.biometric_dialog_authenticated));
+                mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
+                        getDelayAfterAuthenticatedDurationMs());
+                break;
+
+            case STATE_PENDING_CONFIRMATION:
+                removePendingAnimations();
+                mNegativeButton.setText(R.string.cancel);
+                mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
+                mPositiveButton.setEnabled(true);
+                mPositiveButton.setVisibility(View.VISIBLE);
+                mIndicatorView.setTextColor(mTextColorHint);
+                mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
+                mIndicatorView.setVisibility(View.VISIBLE);
+                break;
+
+            case STATE_ERROR:
+                if (mSize == AuthDialog.SIZE_SMALL) {
+                    updateSize(AuthDialog.SIZE_MEDIUM);
+                }
+                break;
+
+            default:
+                Log.w(TAG, "Unhandled state: " + newState);
+                break;
+        }
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        mState = newState;
+    }
+
+    public void onDialogAnimatedIn() {
+        updateState(STATE_AUTHENTICATING);
+    }
+
+    public void onAuthenticationSucceeded() {
+        removePendingAnimations();
+        if (mRequireConfirmation) {
+            updateState(STATE_PENDING_CONFIRMATION);
+        } else {
+            updateState(STATE_AUTHENTICATED);
+        }
+    }
+
+    public void onAuthenticationFailed(String failureReason) {
+        showTemporaryMessage(failureReason, mResetErrorRunnable);
+        updateState(STATE_ERROR);
+    }
+
+    public void onError(String error) {
+        showTemporaryMessage(error, mResetErrorRunnable);
+        updateState(STATE_ERROR);
+
+        mHandler.postDelayed(() -> {
+            mCallback.onAction(Callback.ACTION_ERROR);
+        }, mInjector.getDelayAfterError());
+    }
+
+    public void onHelp(String help) {
+        if (mSize != AuthDialog.SIZE_MEDIUM) {
+            Log.w(TAG, "Help received in size: " + mSize);
+            return;
+        }
+        showTemporaryMessage(help, mResetHelpRunnable);
+        updateState(STATE_HELP);
+    }
+
+    public void onSaveState(@NonNull Bundle outState) {
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
+                mTryAgainButton.getVisibility());
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
+        outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
+                mIndicatorView.getText().toString());
+        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
+                mHandler.hasCallbacks(mResetErrorRunnable));
+        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
+                mHandler.hasCallbacks(mResetHelpRunnable));
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE, mSize);
+    }
+
+    /**
+     * Invoked after inflation but before being attached to window.
+     * @param savedState
+     */
+    public void restoreState(@Nullable Bundle savedState) {
+        mSavedState = savedState;
+    }
+
+    private void setTextOrHide(TextView view, String string) {
+        if (TextUtils.isEmpty(string)) {
+            view.setVisibility(View.GONE);
+        } else {
+            view.setText(string);
+        }
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    private void setText(TextView view, String string) {
+        view.setText(string);
+    }
+
+    // Remove all pending icon and text animations
+    private void removePendingAnimations() {
+        mHandler.removeCallbacks(mResetHelpRunnable);
+        mHandler.removeCallbacks(mResetErrorRunnable);
+    }
+
+    private void showTemporaryMessage(String message, Runnable resetMessageRunnable) {
+        removePendingAnimations();
+        mIndicatorView.setText(message);
+        mIndicatorView.setTextColor(mTextColorError);
+        mIndicatorView.setVisibility(View.VISIBLE);
+        mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        onFinishInflateInternal();
+    }
+
+    /**
+     * After inflation, but before things like restoreState, onAttachedToWindow, etc.
+     */
+    @VisibleForTesting
+    void onFinishInflateInternal() {
+        mTitleView = mInjector.getTitleView();
+        mSubtitleView = mInjector.getSubtitleView();
+        mDescriptionView = mInjector.getDescriptionView();
+        mIconView = mInjector.getIconView();
+        mIndicatorView = mInjector.getIndicatorView();
+        mNegativeButton = mInjector.getNegativeButton();
+        mPositiveButton = mInjector.getPositiveButton();
+        mTryAgainButton = mInjector.getTryAgainButton();
+
+        mNegativeButton.setOnClickListener((view) -> {
+            if (mState == STATE_PENDING_CONFIRMATION) {
+                mCallback.onAction(Callback.ACTION_USER_CANCELED);
+            } else {
+                mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
+            }
+        });
+
+        mPositiveButton.setOnClickListener((view) -> {
+            updateState(STATE_AUTHENTICATED);
+        });
+
+        mTryAgainButton.setOnClickListener((view) -> {
+            updateState(STATE_AUTHENTICATING);
+            mCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN);
+            mTryAgainButton.setVisibility(View.GONE);
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        });
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        onAttachedToWindowInternal();
+    }
+
+    /**
+     * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
+     * invoked.
+     */
+    @VisibleForTesting
+    void onAttachedToWindowInternal() {
+        setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE));
+        setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
+
+        setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE));
+        setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+
+        if (mSavedState == null) {
+            updateState(STATE_AUTHENTICATING_ANIMATING_IN);
+        } else {
+            // Restore as much state as possible first
+            updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+
+            // Restore positive button state
+            mTryAgainButton.setVisibility(
+                    mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
+        // the new dialog is restored.
+        mHandler.removeCallbacksAndMessages(null /* all */);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        final int height = MeasureSpec.getSize(heightMeasureSpec);
+        final int newWidth = Math.min(width, height);
+
+        int totalHeight = 0;
+        final int numChildren = getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+
+            if (child.getId() == R.id.biometric_icon) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+            } else if (child.getId() == R.id.button_bar) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
+            } else {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+            }
+
+            if (child.getVisibility() != View.GONE) {
+                totalHeight += child.getMeasuredHeight();
+            }
+        }
+
+        // Use the new width so it's centered horizontally
+        setMeasuredDimension(newWidth, totalHeight);
+
+        mMediumHeight = totalHeight;
+        mMediumWidth = getMeasuredWidth();
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        onLayoutInternal();
+    }
+
+    /**
+     * Contains all the testable logic that should be invoked when
+     * {@link #onLayout(boolean, int, int, int, int)}, is invoked.
+     */
+    @VisibleForTesting
+    void onLayoutInternal() {
+        // Start with initial size only once. Subsequent layout changes don't matter since we
+        // only care about the initial icon position.
+        if (mIconOriginalY == 0) {
+            mIconOriginalY = mIconView.getY();
+            if (mSavedState == null) {
+                updateSize(!mRequireConfirmation && supportsSmallDialog() ? AuthDialog.SIZE_SMALL
+                        : AuthDialog.SIZE_MEDIUM);
+            } else {
+                updateSize(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE));
+
+                // Restore indicator text state only after size has been restored
+                final String indicatorText =
+                        mSavedState.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING);
+                if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING)) {
+                    onHelp(indicatorText);
+                } else if (mSavedState.getBoolean(
+                        AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)) {
+                    onAuthenticationFailed(indicatorText);
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
new file mode 100644
index 0000000..6555c75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -0,0 +1,453 @@
+/*
+ * 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.biometrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Top level container/controller for the BiometricPrompt UI.
+ */
+public class AuthContainerView extends LinearLayout
+        implements AuthDialog, WakefulnessLifecycle.Observer {
+
+    private static final String TAG = "BiometricPrompt/AuthContainerView";
+    private static final int ANIMATION_DURATION_SHOW_MS = 250;
+    private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+
+    static final int STATE_UNKNOWN = 0;
+    static final int STATE_ANIMATING_IN = 1;
+    static final int STATE_PENDING_DISMISS = 2;
+    static final int STATE_SHOWING = 3;
+    static final int STATE_ANIMATING_OUT = 4;
+    static final int STATE_GONE = 5;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
+            STATE_ANIMATING_OUT, STATE_GONE})
+    @interface ContainerState {}
+
+    final Config mConfig;
+    private final IBinder mWindowToken = new Binder();
+    private final WindowManager mWindowManager;
+    private final AuthPanelController mPanelController;
+    private final Interpolator mLinearOutSlowIn;
+    @VisibleForTesting final BiometricCallback mBiometricCallback;
+
+    private final ViewGroup mContainerView;
+    private final AuthBiometricView mBiometricView;
+
+    private final ImageView mBackgroundView;
+    private final ScrollView mScrollView;
+    private final View mPanelView;
+
+    private final float mTranslationY;
+
+    @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
+
+    private @ContainerState int mContainerState = STATE_UNKNOWN;
+
+    // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
+    @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+
+    static class Config {
+        Context mContext;
+        AuthDialogCallback mCallback;
+        Bundle mBiometricPromptBundle;
+        boolean mRequireConfirmation;
+        int mUserId;
+        String mOpPackageName;
+        int mModalityMask;
+        boolean mSkipIntro;
+    }
+
+    public static class Builder {
+        Config mConfig;
+
+        public Builder(Context context) {
+            mConfig = new Config();
+            mConfig.mContext = context;
+        }
+
+        public Builder setCallback(AuthDialogCallback callback) {
+            mConfig.mCallback = callback;
+            return this;
+        }
+
+        public Builder setBiometricPromptBundle(Bundle bundle) {
+            mConfig.mBiometricPromptBundle = bundle;
+            return this;
+        }
+
+        public Builder setRequireConfirmation(boolean requireConfirmation) {
+            mConfig.mRequireConfirmation = requireConfirmation;
+            return this;
+        }
+
+        public Builder setUserId(int userId) {
+            mConfig.mUserId = userId;
+            return this;
+        }
+
+        public Builder setOpPackageName(String opPackageName) {
+            mConfig.mOpPackageName = opPackageName;
+            return this;
+        }
+
+        public Builder setSkipIntro(boolean skip) {
+            mConfig.mSkipIntro = skip;
+            return this;
+        }
+
+        public AuthContainerView build(int modalityMask) {
+            mConfig.mModalityMask = modalityMask;
+            return new AuthContainerView(mConfig);
+        }
+    }
+
+    @VisibleForTesting
+    final class BiometricCallback implements AuthBiometricView.Callback {
+        @Override
+        public void onAction(int action) {
+            switch (action) {
+                case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
+                    animateAway(AuthDialogCallback.DISMISSED_AUTHENTICATED);
+                    break;
+                case AuthBiometricView.Callback.ACTION_USER_CANCELED:
+                    animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+                    break;
+                case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
+                    animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+                    break;
+                case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+                    mConfig.mCallback.onTryAgainPressed();
+                    break;
+                case AuthBiometricView.Callback.ACTION_ERROR:
+                    animateAway(AuthDialogCallback.DISMISSED_ERROR);
+                    break;
+                default:
+                    Log.e(TAG, "Unhandled action: " + action);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    AuthContainerView(Config config) {
+        super(config.mContext);
+
+        mConfig = config;
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+
+        mTranslationY = getResources()
+                .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
+        mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
+        mBiometricCallback = new BiometricCallback();
+
+        final LayoutInflater factory = LayoutInflater.from(mContext);
+        mContainerView = (ViewGroup) factory.inflate(
+                R.layout.auth_container_view, this, false /* attachToRoot */);
+
+        mPanelView = mContainerView.findViewById(R.id.panel);
+        mPanelController = new AuthPanelController(mContext, mPanelView);
+
+        // TODO: Update with new controllers if multi-modal authentication can occur simultaneously
+        if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
+            mBiometricView = (AuthBiometricFingerprintView)
+                    factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
+        } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
+            mBiometricView = (AuthBiometricFaceView)
+                    factory.inflate(R.layout.auth_biometric_face_view, null, false);
+        } else {
+            Log.e(TAG, "Unsupported modality mask: " + config.mModalityMask);
+            mBiometricView = null;
+            mBackgroundView = null;
+            mScrollView = null;
+            return;
+        }
+
+        mBackgroundView = mContainerView.findViewById(R.id.background);
+
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (userManager.isManagedProfile(mConfig.mUserId)) {
+            final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
+                    mContext.getTheme());
+            image.setColorFilter(dpm.getOrganizationColorForUser(mConfig.mUserId),
+                    PorterDuff.Mode.DARKEN);
+            mBackgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+            mBackgroundView.setImageDrawable(image);
+        }
+
+        mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
+        mBiometricView.setPanelController(mPanelController);
+        mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle);
+        mBiometricView.setCallback(mBiometricCallback);
+        mBiometricView.setBackgroundView(mBackgroundView);
+
+        mScrollView = mContainerView.findViewById(R.id.scrollview);
+        mScrollView.addView(mBiometricView);
+        addView(mContainerView);
+
+        setOnKeyListener((v, keyCode, event) -> {
+            if (keyCode != KeyEvent.KEYCODE_BACK) {
+                return false;
+            }
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+            }
+            return true;
+        });
+
+        setFocusableInTouchMode(true);
+        requestFocus();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mWakefulnessLifecycle.addObserver(this);
+
+        if (mConfig.mSkipIntro) {
+            mContainerState = STATE_SHOWING;
+        } else {
+            mContainerState = STATE_ANIMATING_IN;
+            // The background panel and content are different views since we need to be able to
+            // animate them separately in other places.
+            mPanelView.setY(mTranslationY);
+            mScrollView.setY(mTranslationY);
+
+            setAlpha(0f);
+            postOnAnimation(() -> {
+                mPanelView.animate()
+                        .translationY(0)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .withEndAction(this::onDialogAnimatedIn)
+                        .start();
+                mScrollView.animate()
+                        .translationY(0)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .start();
+                animate()
+                        .alpha(1f)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .start();
+            });
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mWakefulnessLifecycle.removeObserver(this);
+    }
+
+    @Override
+    public void onStartedGoingToSleep() {
+        animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+    }
+
+    @Override
+    public void show(WindowManager wm, @Nullable Bundle savedState) {
+        mBiometricView.restoreState(savedState);
+        wm.addView(this, getLayoutParams(mWindowToken));
+    }
+
+    @Override
+    public void dismissWithoutCallback(boolean animate) {
+        if (animate) {
+            animateAway(false /* sendReason */, 0 /* reason */);
+        } else {
+            removeWindowIfAttached();
+        }
+    }
+
+    @Override
+    public void dismissFromSystemServer() {
+        removeWindowIfAttached();
+    }
+
+    @Override
+    public void onAuthenticationSucceeded() {
+        mBiometricView.onAuthenticationSucceeded();
+    }
+
+    @Override
+    public void onAuthenticationFailed(String failureReason) {
+        mBiometricView.onAuthenticationFailed(failureReason);
+    }
+
+    @Override
+    public void onHelp(String help) {
+        mBiometricView.onHelp(help);
+    }
+
+    @Override
+    public void onError(String error) {
+        mBiometricView.onError(error);
+    }
+
+    @Override
+    public void onSaveState(@NonNull Bundle outState) {
+        outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+        mBiometricView.onSaveState(outState);
+    }
+
+    @Override
+    public String getOpPackageName() {
+        return mConfig.mOpPackageName;
+    }
+
+    @VisibleForTesting
+    void animateAway(int reason) {
+        animateAway(true /* sendReason */, reason);
+    }
+
+    private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
+        if (mContainerState == STATE_ANIMATING_IN) {
+            Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
+            mContainerState = STATE_PENDING_DISMISS;
+            return;
+        }
+
+        if (mContainerState == STATE_ANIMATING_OUT) {
+            Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
+            return;
+        }
+        mContainerState = STATE_ANIMATING_OUT;
+
+        if (sendReason) {
+            mPendingCallbackReason = reason;
+        } else {
+            mPendingCallbackReason = null;
+        }
+
+        final Runnable endActionRunnable = () -> {
+            setVisibility(View.INVISIBLE);
+            removeWindowIfAttached();
+        };
+
+        postOnAnimation(() -> {
+            mPanelView.animate()
+                    .translationY(mTranslationY)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .withEndAction(endActionRunnable)
+                    .start();
+            mScrollView.animate()
+                    .translationY(mTranslationY)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .start();
+            animate()
+                    .alpha(0f)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .start();
+        });
+    }
+
+    private void sendPendingCallbackIfNotNull() {
+        Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
+        if (mPendingCallbackReason != null) {
+            mConfig.mCallback.onDismissed(mPendingCallbackReason);
+            mPendingCallbackReason = null;
+        }
+    }
+
+    private void removeWindowIfAttached() {
+        sendPendingCallbackIfNotNull();
+
+        if (mContainerState == STATE_GONE) {
+            return;
+        }
+        mContainerState = STATE_GONE;
+        mWindowManager.removeView(this);
+    }
+
+    private void onDialogAnimatedIn() {
+        if (mContainerState == STATE_PENDING_DISMISS) {
+            Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
+            animateAway(false /* sendReason */, 0);
+            return;
+        }
+        mContainerState = STATE_SHOWING;
+        mBiometricView.onDialogAnimatedIn();
+    }
+
+    /**
+     * @param windowToken token for the window
+     * @return
+     */
+    public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("BiometricPrompt");
+        lp.token = windowToken;
+        return lp;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
new file mode 100644
index 0000000..d10a3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.systemui.SystemUI;
+import com.android.systemui.statusbar.CommandQueue;
+
+import java.util.List;
+
+/**
+ * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
+ * appropriate biometric UI (e.g. BiometricDialogView).
+ */
+public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+        AuthDialogCallback {
+
+    private static final String TAG = "BiometricPrompt/AuthController";
+    private static final boolean DEBUG = true;
+
+    private final Injector mInjector;
+
+    // TODO: These should just be saved from onSaveState
+    private SomeArgs mCurrentDialogArgs;
+    @VisibleForTesting
+    AuthDialog mCurrentDialog;
+
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private WindowManager mWindowManager;
+    @VisibleForTesting
+    IActivityTaskManager mActivityTaskManager;
+    @VisibleForTesting
+    BiometricTaskStackListener mTaskStackListener;
+    @VisibleForTesting
+    IBiometricServiceReceiverInternal mReceiver;
+
+    public class BiometricTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            mHandler.post(mTaskStackChangedRunnable);
+        }
+    }
+
+    private final Runnable mTaskStackChangedRunnable = () -> {
+        if (mCurrentDialog != null) {
+            try {
+                final String clientPackage = mCurrentDialog.getOpPackageName();
+                Log.w(TAG, "Task stack changed, current client: " + clientPackage);
+                final List<ActivityManager.RunningTaskInfo> runningTasks =
+                        mActivityTaskManager.getTasks(1);
+                if (!runningTasks.isEmpty()) {
+                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+                    if (!topPackage.contentEquals(clientPackage)) {
+                        Log.w(TAG, "Evicting client due to: " + topPackage);
+                        mCurrentDialog.dismissWithoutCallback(true /* animate */);
+                        mCurrentDialog = null;
+                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+                        mReceiver = null;
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Remote exception", e);
+            }
+        }
+    };
+
+    @Override
+    public void onTryAgainPressed() {
+        try {
+            mReceiver.onTryAgainPressed();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException when handling try again", e);
+        }
+    }
+
+    @Override
+    public void onDismissed(@DismissedReason int reason) {
+        switch (reason) {
+            case AuthDialogCallback.DISMISSED_USER_CANCELED:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+                break;
+
+            case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+                break;
+
+            case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
+                break;
+
+            case AuthDialogCallback.DISMISSED_AUTHENTICATED:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
+                break;
+
+            case AuthDialogCallback.DISMISSED_ERROR:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
+                break;
+
+            case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+                break;
+
+            default:
+                Log.e(TAG, "Unhandled reason: " + reason);
+                break;
+        }
+    }
+
+    private void sendResultAndCleanUp(@DismissedReason int reason) {
+        if (mReceiver == null) {
+            Log.e(TAG, "Receiver is null");
+            return;
+        }
+        try {
+            mReceiver.onDialogDismissed(reason);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Remote exception", e);
+        }
+        onDialogDismissed(reason);
+    }
+
+    public static class Injector {
+        IActivityTaskManager getActivityTaskManager() {
+            return ActivityTaskManager.getService();
+        }
+    }
+
+    public AuthController() {
+        this(new Injector());
+    }
+
+    @VisibleForTesting
+    AuthController(Injector injector) {
+        mInjector = injector;
+    }
+
+    @Override
+    public void start() {
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
+                || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
+            getComponent(CommandQueue.class).addCallback(this);
+            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+            mActivityTaskManager = mInjector.getActivityTaskManager();
+
+            try {
+                mTaskStackListener = new BiometricTaskStackListener();
+                mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Unable to register task stack listener", e);
+            }
+        }
+    }
+
+    @Override
+    public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+            int type, boolean requireConfirmation, int userId, String opPackageName) {
+        if (DEBUG) {
+            Log.d(TAG, "showBiometricDialog, type: " + type
+                    + ", requireConfirmation: " + requireConfirmation);
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = bundle;
+        args.arg2 = receiver;
+        args.argi1 = type;
+        args.arg3 = requireConfirmation;
+        args.argi2 = userId;
+        args.arg4 = opPackageName;
+
+        boolean skipAnimation = false;
+        if (mCurrentDialog != null) {
+            Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
+            skipAnimation = true;
+        }
+        showDialog(args, skipAnimation, null /* savedState */);
+    }
+
+    @Override
+    public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+        if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
+                + " reason: " + failureReason);
+
+        if (authenticated) {
+            mCurrentDialog.onAuthenticationSucceeded();
+        } else {
+            mCurrentDialog.onAuthenticationFailed(failureReason);
+        }
+    }
+
+    @Override
+    public void onBiometricHelp(String message) {
+        if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
+
+        mCurrentDialog.onHelp(message);
+    }
+
+    @Override
+    public void onBiometricError(String error) {
+        if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
+        mCurrentDialog.onError(error);
+    }
+
+    @Override
+    public void hideBiometricDialog() {
+        if (DEBUG) Log.d(TAG, "hideBiometricDialog");
+
+        mCurrentDialog.dismissFromSystemServer();
+    }
+
+    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
+        mCurrentDialogArgs = args;
+        final int type = args.argi1;
+        final Bundle biometricPromptBundle = (Bundle) args.arg1;
+        final boolean requireConfirmation = (boolean) args.arg3;
+        final int userId = args.argi2;
+        final String opPackageName = (String) args.arg4;
+
+        // Create a new dialog but do not replace the current one yet.
+        final AuthDialog newDialog = buildDialog(
+                biometricPromptBundle,
+                requireConfirmation,
+                userId,
+                type,
+                opPackageName,
+                skipAnimation);
+
+        if (newDialog == null) {
+            Log.e(TAG, "Unsupported type: " + type);
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "showDialog, "
+                    + " savedState: " + savedState
+                    + " mCurrentDialog: " + mCurrentDialog
+                    + " newDialog: " + newDialog
+                    + " type: " + type);
+        }
+
+        if (mCurrentDialog != null) {
+            // If somehow we're asked to show a dialog, the old one doesn't need to be animated
+            // away. This can happen if the app cancels and re-starts auth during configuration
+            // change. This is ugly because we also have to do things on onConfigurationChanged
+            // here.
+            mCurrentDialog.dismissWithoutCallback(false /* animate */);
+        }
+
+        mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
+        mCurrentDialog = newDialog;
+        mCurrentDialog.show(mWindowManager, savedState);
+    }
+
+    private void onDialogDismissed(@DismissedReason int reason) {
+        if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
+        if (mCurrentDialog == null) {
+            Log.w(TAG, "Dialog already dismissed");
+        }
+        mReceiver = null;
+        mCurrentDialog = null;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        // Save the state of the current dialog (buttons showing, etc)
+        if (mCurrentDialog != null) {
+            final Bundle savedState = new Bundle();
+            mCurrentDialog.onSaveState(savedState);
+            mCurrentDialog.dismissWithoutCallback(false /* animate */);
+            mCurrentDialog = null;
+
+            // Only show the dialog if necessary. If it was animating out, the dialog is supposed
+            // to send its pending callback immediately.
+            if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
+                    != AuthContainerView.STATE_ANIMATING_OUT) {
+                showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
+            }
+        }
+    }
+
+    protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
+            int userId, int type, String opPackageName, boolean skipIntro) {
+        return new AuthContainerView.Builder(mContext)
+                .setCallback(this)
+                .setBiometricPromptBundle(biometricPromptBundle)
+                .setRequireConfirmation(requireConfirmation)
+                .setUserId(userId)
+                .setOpPackageName(opPackageName)
+                .setSkipIntro(skipIntro)
+                .build(type);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
new file mode 100644
index 0000000..edb2953
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -0,0 +1,104 @@
+/*
+ * 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.biometrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for the biometric dialog UI.
+ */
+public interface AuthDialog {
+
+    String KEY_CONTAINER_STATE = "container_state";
+
+    String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
+    String KEY_BIOMETRIC_STATE = "state";
+    String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
+    String KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING = "error_is_temporary";
+    String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
+    String KEY_BIOMETRIC_DIALOG_SIZE = "size";
+
+    int SIZE_UNKNOWN = 0;
+    int SIZE_SMALL = 1;
+    int SIZE_MEDIUM = 2;
+    int SIZE_LARGE = 3;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
+    @interface DialogSize {}
+
+    /**
+     * Animation duration, e.g. small to medium dialog, icon translation, etc.
+     */
+    int ANIMATE_DURATION_MS = 150;
+
+    /**
+     * Show the dialog.
+     * @param wm
+     */
+    void show(WindowManager wm, @Nullable Bundle savedState);
+
+    /**
+     * Dismiss the dialog without sending a callback.
+     */
+    void dismissWithoutCallback(boolean animate);
+
+    /**
+     * Dismiss the dialog. Animate away.
+     */
+    void dismissFromSystemServer();
+
+    /**
+     * Biometric authenticated. May be pending user confirmation, or completed.
+     */
+    void onAuthenticationSucceeded();
+
+    /**
+     * Authentication failed (reject, timeout). Dialog stays showing.
+     * @param failureReason
+     */
+    void onAuthenticationFailed(String failureReason);
+
+    /**
+     * Authentication rejected, or help message received.
+     * @param help
+     */
+    void onHelp(String help);
+
+    /**
+     * Authentication failed. Dialog going away.
+     * @param error
+     */
+    void onError(String error);
+
+    /**
+     * Save the current state.
+     * @param outState
+     */
+    void onSaveState(@NonNull Bundle outState);
+
+    /**
+     * Get the client's package name
+     */
+    String getOpPackageName();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
new file mode 100644
index 0000000..70752f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.IntDef;
+
+/**
+ * Callback interface for dialog views. These should be implemented by the controller (e.g.
+ * FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
+ */
+public interface AuthDialogCallback {
+
+    int DISMISSED_USER_CANCELED = 1;
+    int DISMISSED_BUTTON_NEGATIVE = 2;
+    int DISMISSED_BUTTON_POSITIVE = 3;
+
+    int DISMISSED_AUTHENTICATED = 4;
+    int DISMISSED_ERROR = 5;
+    int DISMISSED_BY_SYSTEM_SERVER = 6;
+
+    @IntDef({DISMISSED_USER_CANCELED,
+            DISMISSED_BUTTON_NEGATIVE,
+            DISMISSED_BUTTON_POSITIVE,
+            DISMISSED_AUTHENTICATED,
+            DISMISSED_ERROR,
+            DISMISSED_BY_SYSTEM_SERVER})
+    @interface DismissedReason {}
+
+    /**
+     * Invoked when the dialog is dismissed
+     * @param reason
+     */
+    void onDismissed(@DismissedReason int reason);
+
+    /**
+     * Invoked when the "try again" button is clicked
+     */
+    void onTryAgainPressed();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
new file mode 100644
index 0000000..55ba049
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -0,0 +1,105 @@
+/*
+ * 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.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthDialog;
+
+/**
+ * Controls the back panel and its animations for the BiometricPrompt UI.
+ */
+public class AuthPanelController extends ViewOutlineProvider {
+
+    private static final String TAG = "BiometricPrompt/AuthPanelController";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final View mPanelView;
+    private final float mCornerRadius;
+    private final int mBiometricMargin;
+
+    private int mContainerWidth;
+    private int mContainerHeight;
+
+    private int mContentWidth;
+    private int mContentHeight;
+
+    @Override
+    public void getOutline(View view, Outline outline) {
+        final int left = (mContainerWidth - mContentWidth) / 2;
+        final int right = mContainerWidth - left;
+        final int top = mContentHeight < mContainerHeight
+                ? mContainerHeight - mContentHeight - mBiometricMargin
+                : mBiometricMargin;
+        final int bottom = mContainerHeight - mBiometricMargin;
+        outline.setRoundRect(left, top, right, bottom, mCornerRadius);
+    }
+
+    public void setContainerDimensions(int containerWidth, int containerHeight) {
+        if (DEBUG) {
+            Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight);
+        }
+        mContainerWidth = containerWidth;
+        mContainerHeight = containerHeight;
+    }
+
+    public void updateForContentDimensions(int contentWidth, int contentHeight, boolean animate) {
+        if (DEBUG) {
+            Log.v(TAG, "Content Width: " + contentWidth
+                    + " Height: " + contentHeight
+                    + " Animate: " + animate);
+        }
+
+        if (mContainerWidth == 0 || mContainerHeight == 0) {
+            Log.w(TAG, "Not done measuring yet");
+            return;
+        }
+
+        if (animate) {
+            ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight);
+            heightAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            heightAnimator.addUpdateListener((animation) -> {
+                mContentHeight = (int) animation.getAnimatedValue();
+                mPanelView.invalidateOutline();
+            });
+            heightAnimator.start();
+        } else {
+            mContentWidth = contentWidth;
+            mContentHeight = contentHeight;
+            mPanelView.invalidateOutline();
+        }
+    }
+
+    AuthPanelController(Context context, View panelView) {
+        mContext = context;
+        mPanelView = panelView;
+        mCornerRadius = context.getResources()
+                .getDimension(R.dimen.biometric_dialog_corner_size);
+        mBiometricMargin = (int) context.getResources()
+                .getDimension(R.dimen.biometric_dialog_border_padding);
+        mPanelView.setOutlineProvider(this);
+        mPanelView.setClipToOutline(true);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
deleted file mode 100644
index d4baefd..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
+++ /dev/null
@@ -1,108 +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.biometrics;
-
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import com.android.systemui.biometrics.ui.BiometricDialogView;
-
-/**
- * Interface for the biometric dialog UI.
- */
-public interface BiometricDialog {
-
-    // TODO: Clean up save/restore state
-    String[] KEYS_TO_BACKUP = {
-            BiometricPrompt.KEY_TITLE,
-            BiometricPrompt.KEY_USE_DEFAULT_TITLE,
-            BiometricPrompt.KEY_SUBTITLE,
-            BiometricPrompt.KEY_DESCRIPTION,
-            BiometricPrompt.KEY_POSITIVE_TEXT,
-            BiometricPrompt.KEY_NEGATIVE_TEXT,
-            BiometricPrompt.KEY_REQUIRE_CONFIRMATION,
-            BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL,
-            BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL,
-
-            BiometricDialogView.KEY_TRY_AGAIN_VISIBILITY,
-            BiometricDialogView.KEY_CONFIRM_VISIBILITY,
-            BiometricDialogView.KEY_CONFIRM_ENABLED,
-            BiometricDialogView.KEY_STATE,
-            BiometricDialogView.KEY_ERROR_TEXT_VISIBILITY,
-            BiometricDialogView.KEY_ERROR_TEXT_STRING,
-            BiometricDialogView.KEY_ERROR_TEXT_IS_TEMPORARY,
-            BiometricDialogView.KEY_ERROR_TEXT_COLOR,
-    };
-
-    /**
-     * Show the dialog.
-     * @param wm
-     * @param skipIntroAnimation
-     */
-    void show(WindowManager wm, boolean skipIntroAnimation);
-
-    /**
-     * Dismiss the dialog without sending a callback.
-     */
-    void dismissWithoutCallback(boolean animate);
-
-    /**
-     * Dismiss the dialog. Animate away.
-     */
-    void dismissFromSystemServer();
-
-    /**
-     * Biometric authenticated. May be pending user confirmation, or completed.
-     */
-    void onAuthenticationSucceeded();
-
-    /**
-     * Authentication failed (reject, timeout). Dialog stays showing.
-     * @param failureReason
-     */
-    void onAuthenticationFailed(String failureReason);
-
-    /**
-     * Authentication rejected, or help message received.
-     * @param help
-     */
-    void onHelp(String help);
-
-    /**
-     * Authentication failed. Dialog going away.
-     * @param error
-     */
-    void onError(String error);
-
-    /**
-     * Save the current state.
-     * @param outState
-     */
-    void onSaveState(Bundle outState);
-
-    /**
-     * Restore a previous state.
-     * @param savedState
-     */
-    void restoreState(Bundle savedState);
-
-    /**
-     * Get the client's package name
-     */
-    String getOpPackageName();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
deleted file mode 100644
index a8e5722..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.biometrics;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
-import android.app.TaskStackListener;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
-import com.android.systemui.SystemUI;
-import com.android.systemui.biometrics.ui.BiometricDialogView;
-import com.android.systemui.statusbar.CommandQueue;
-
-import java.util.List;
-
-/**
- * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
- * appropriate biometric UI (e.g. BiometricDialogView).
- */
-public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
-        DialogViewCallback {
-    private static final String TAG = "BiometricDialogImpl";
-    private static final boolean DEBUG = true;
-
-    private final Injector mInjector;
-
-    // TODO: These should just be saved from onSaveState
-    private SomeArgs mCurrentDialogArgs;
-    @VisibleForTesting
-    BiometricDialog mCurrentDialog;
-
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private WindowManager mWindowManager;
-    @VisibleForTesting
-    IActivityTaskManager mActivityTaskManager;
-    @VisibleForTesting
-    BiometricTaskStackListener mTaskStackListener;
-    @VisibleForTesting
-    IBiometricServiceReceiverInternal mReceiver;
-
-    public class BiometricTaskStackListener extends TaskStackListener {
-        @Override
-        public void onTaskStackChanged() {
-            mHandler.post(mTaskStackChangedRunnable);
-        }
-    }
-
-    private final Runnable mTaskStackChangedRunnable = () -> {
-        if (mCurrentDialog != null) {
-            try {
-                final String clientPackage = mCurrentDialog.getOpPackageName();
-                Log.w(TAG, "Task stack changed, current client: " + clientPackage);
-                final List<ActivityManager.RunningTaskInfo> runningTasks =
-                        mActivityTaskManager.getTasks(1);
-                if (!runningTasks.isEmpty()) {
-                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(clientPackage)) {
-                        Log.w(TAG, "Evicting client due to: " + topPackage);
-                        mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                        mCurrentDialog = null;
-                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
-                        mReceiver = null;
-                    }
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "Remote exception", e);
-            }
-        }
-    };
-
-    @Override
-    public void onTryAgainPressed() {
-        try {
-            mReceiver.onTryAgainPressed();
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException when handling try again", e);
-        }
-    }
-
-    @Override
-    public void onDismissed(@DismissedReason int reason) {
-        switch (reason) {
-            case DialogViewCallback.DISMISSED_USER_CANCELED:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
-                break;
-
-            case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
-                break;
-
-            case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
-                break;
-
-            case DialogViewCallback.DISMISSED_AUTHENTICATED:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
-                break;
-
-            case DialogViewCallback.DISMISSED_ERROR:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
-                break;
-
-            case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
-                break;
-
-            default:
-                Log.e(TAG, "Unhandled reason: " + reason);
-                break;
-        }
-    }
-
-    private void sendResultAndCleanUp(@DismissedReason int reason) {
-        if (mReceiver == null) {
-            Log.e(TAG, "Receiver is null");
-            return;
-        }
-        try {
-            mReceiver.onDialogDismissed(reason);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Remote exception", e);
-        }
-        onDialogDismissed(reason);
-    }
-
-    public static class Injector {
-        IActivityTaskManager getActivityTaskManager() {
-            return ActivityTaskManager.getService();
-        }
-    }
-
-    public BiometricDialogImpl() {
-        this(new Injector());
-    }
-
-    @VisibleForTesting
-    BiometricDialogImpl(Injector injector) {
-        mInjector = injector;
-    }
-
-    @Override
-    public void start() {
-        final PackageManager pm = mContext.getPackageManager();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
-                || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
-                || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
-            getComponent(CommandQueue.class).addCallback(this);
-            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-            mActivityTaskManager = mInjector.getActivityTaskManager();
-
-            try {
-                mTaskStackListener = new BiometricTaskStackListener();
-                mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Unable to register task stack listener", e);
-            }
-        }
-    }
-
-    @Override
-    public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
-            int type, boolean requireConfirmation, int userId, String opPackageName) {
-        if (DEBUG) {
-            Log.d(TAG, "showBiometricDialog, type: " + type
-                    + ", requireConfirmation: " + requireConfirmation);
-        }
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = bundle;
-        args.arg2 = receiver;
-        args.argi1 = type;
-        args.arg3 = requireConfirmation;
-        args.argi2 = userId;
-        args.arg4 = opPackageName;
-
-        boolean skipAnimation = false;
-        if (mCurrentDialog != null) {
-            Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
-            skipAnimation = true;
-        }
-        showDialog(args, skipAnimation, null /* savedState */);
-    }
-
-    @Override
-    public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
-        if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
-                + " reason: " + failureReason);
-
-        if (authenticated) {
-            mCurrentDialog.onAuthenticationSucceeded();
-        } else {
-            mCurrentDialog.onAuthenticationFailed(failureReason);
-        }
-    }
-
-    @Override
-    public void onBiometricHelp(String message) {
-        if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
-
-        mCurrentDialog.onHelp(message);
-    }
-
-    @Override
-    public void onBiometricError(String error) {
-        if (DEBUG) Log.d(TAG, "onBiometricError: " + error);
-        mCurrentDialog.onError(error);
-    }
-
-    @Override
-    public void hideBiometricDialog() {
-        if (DEBUG) Log.d(TAG, "hideBiometricDialog");
-
-        mCurrentDialog.dismissFromSystemServer();
-    }
-
-    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
-        mCurrentDialogArgs = args;
-        final int type = args.argi1;
-        final Bundle biometricPromptBundle = (Bundle) args.arg1;
-        final boolean requireConfirmation = (boolean) args.arg3;
-        final int userId = args.argi2;
-        final String opPackageName = (String) args.arg4;
-
-        // Create a new dialog but do not replace the current one yet.
-        final BiometricDialog newDialog = buildDialog(
-                biometricPromptBundle,
-                requireConfirmation,
-                userId,
-                type,
-                opPackageName);
-
-        if (newDialog == null) {
-            Log.e(TAG, "Unsupported type: " + type);
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "showDialog, "
-                    + " savedState: " + savedState
-                    + " mCurrentDialog: " + mCurrentDialog
-                    + " newDialog: " + newDialog
-                    + " type: " + type);
-        }
-
-        if (savedState != null) {
-            // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
-            // even though it may be removed / re-created again
-            newDialog.restoreState(savedState);
-        } else if (mCurrentDialog != null) {
-            // If somehow we're asked to show a dialog, the old one doesn't need to be animated
-            // away. This can happen if the app cancels and re-starts auth during configuration
-            // change. This is ugly because we also have to do things on onConfigurationChanged
-            // here.
-            mCurrentDialog.dismissWithoutCallback(false /* animate */);
-        }
-
-        mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
-        mCurrentDialog = newDialog;
-        mCurrentDialog.show(mWindowManager, skipAnimation);
-    }
-
-    private void onDialogDismissed(@DismissedReason int reason) {
-        if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
-        if (mCurrentDialog == null) {
-            Log.w(TAG, "Dialog already dismissed");
-        }
-        mReceiver = null;
-        mCurrentDialog = null;
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        // Save the state of the current dialog (buttons showing, etc)
-        if (mCurrentDialog != null) {
-            final Bundle savedState = new Bundle();
-            mCurrentDialog.onSaveState(savedState);
-            mCurrentDialog.dismissWithoutCallback(false /* animate */);
-            mCurrentDialog = null;
-
-            showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
-        }
-    }
-
-    protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
-            boolean requireConfirmation, int userId, int type, String opPackageName) {
-        return new BiometricDialogView.Builder(mContext)
-                .setCallback(this)
-                .setBiometricPromptBundle(biometricPromptBundle)
-                .setRequireConfirmation(requireConfirmation)
-                .setUserId(userId)
-                .setOpPackageName(opPackageName)
-                .build(type);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
deleted file mode 100644
index b65d1e8..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.IntDef;
-
-/**
- * Callback interface for dialog views. These should be implemented by the controller (e.g.
- * FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
- */
-public interface DialogViewCallback {
-
-    int DISMISSED_USER_CANCELED = 1;
-    int DISMISSED_BUTTON_NEGATIVE = 2;
-    int DISMISSED_BUTTON_POSITIVE = 3;
-
-    int DISMISSED_AUTHENTICATED = 4;
-    int DISMISSED_ERROR = 5;
-    int DISMISSED_BY_SYSTEM_SERVER = 6;
-
-    @IntDef({DISMISSED_USER_CANCELED,
-            DISMISSED_BUTTON_NEGATIVE,
-            DISMISSED_BUTTON_POSITIVE,
-            DISMISSED_AUTHENTICATED,
-            DISMISSED_ERROR,
-            DISMISSED_BY_SYSTEM_SERVER})
-    @interface DismissedReason {}
-
-    /**
-     * Invoked when the dialog is dismissed
-     * @param reason
-     */
-    void onDismissed(@DismissedReason int reason);
-
-    /**
-     * Invoked when the "try again" button is clicked
-     */
-    void onTryAgainPressed();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
new file mode 100644
index 0000000..e00cf6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.biometrics;
+
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+public class Utils {
+    static float dpToPixels(Context context, float dp) {
+        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    static float pixelsToDp(Context context, float pixels) {
+        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
+        if (!am.isEnabled()) {
+            return;
+        }
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
+        view.sendAccessibilityEventUnchecked(event);
+        view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
deleted file mode 100644
index 2904755..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
+++ /dev/null
@@ -1,956 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.Interpolator;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.biometrics.BiometricDialog;
-import com.android.systemui.biometrics.DialogViewCallback;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.util.leak.RotationUtils;
-
-/**
- * Abstract base class. Shows a dialog for BiometricPrompt.
- */
-public abstract class BiometricDialogView extends LinearLayout implements BiometricDialog {
-
-    private static final String TAG = "BiometricDialogView";
-
-    public static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
-    public static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
-    public static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled";
-    public static final String KEY_STATE = "key_state";
-    public static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility";
-    public static final String KEY_ERROR_TEXT_STRING = "key_error_text_string";
-    public static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary";
-    public static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color";
-    public static final String KEY_DIALOG_SIZE = "key_dialog_size";
-
-    private static final int ANIMATION_DURATION_SHOW = 250; // ms
-    private static final int ANIMATION_DURATION_AWAY = 350; // ms
-
-    protected static final int MSG_RESET_MESSAGE = 1;
-
-    protected static final int STATE_IDLE = 0;
-    protected static final int STATE_AUTHENTICATING = 1;
-    protected static final int STATE_ERROR = 2;
-    protected static final int STATE_PENDING_CONFIRMATION = 3;
-    protected static final int STATE_AUTHENTICATED = 4;
-
-    // Dialog layout/animation
-    private static final int IMPLICIT_Y_PADDING = 16; // dp
-    private static final int GROW_DURATION = 150; // ms
-    private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
-    @VisibleForTesting static final int SIZE_UNKNOWN = 0;
-    @VisibleForTesting static final int SIZE_SMALL = 1;
-    @VisibleForTesting static final int SIZE_GROWING = 2;
-    @VisibleForTesting static final int SIZE_BIG = 3;
-    @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_GROWING, SIZE_BIG})
-    @interface DialogSize {}
-
-    @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final AccessibilityManager mAccessibilityManager;
-    private final IBinder mWindowToken = new Binder();
-    private final Interpolator mLinearOutSlowIn;
-    private final WindowManager mWindowManager;
-    private final UserManager mUserManager;
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final float mAnimationTranslationOffset;
-    private final int mErrorColor;
-    private final float mDialogWidth;
-    protected final DialogViewCallback mCallback;
-    private final DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
-
-    protected final ViewGroup mLayout;
-    protected final LinearLayout mDialog;
-    @VisibleForTesting final TextView mTitleText;
-    @VisibleForTesting final TextView mSubtitleText;
-    @VisibleForTesting final TextView mDescriptionText;
-    @VisibleForTesting final ImageView mBiometricIcon;
-    @VisibleForTesting final TextView mErrorText;
-    @VisibleForTesting final Button mPositiveButton;
-    @VisibleForTesting final Button mNegativeButton;
-    @VisibleForTesting final Button mTryAgainButton;
-
-    protected final int mTextColor;
-
-    private Bundle mBundle;
-    private Bundle mRestoredState;
-    private String mOpPackageName;
-
-    private int mState = STATE_IDLE;
-    private boolean mWasForceRemoved;
-    private boolean mSkipIntro;
-    protected boolean mRequireConfirmation;
-    private int mUserId; // used to determine if we should show work background
-    private @DialogSize int mSize;
-    private float mIconOriginalY;
-
-    private boolean mCompletedAnimatingIn;
-    private boolean mPendingDismissDialog;
-
-    protected abstract int getHintStringResourceId();
-    protected abstract int getAuthenticatedAccessibilityResourceId();
-    protected abstract int getIconDescriptionResourceId();
-    protected abstract int getDelayAfterAuthenticatedDurationMs();
-    protected abstract boolean shouldGrayAreaDismissDialog();
-    protected abstract void handleResetMessage();
-    protected abstract void updateIcon(int oldState, int newState);
-    protected abstract boolean supportsSmallDialog();
-
-    private final Runnable mShowAnimationRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mLayout.animate()
-                    .alpha(1f)
-                    .setDuration(ANIMATION_DURATION_SHOW)
-                    .setInterpolator(mLinearOutSlowIn)
-                    .withLayer()
-                    .start();
-            mDialog.animate()
-                    .translationY(0)
-                    .setDuration(ANIMATION_DURATION_SHOW)
-                    .setInterpolator(mLinearOutSlowIn)
-                    .withLayer()
-                    .withEndAction(() -> onDialogAnimatedIn())
-                    .start();
-        }
-    };
-
-    @VisibleForTesting
-    final WakefulnessLifecycle.Observer mWakefulnessObserver =
-            new WakefulnessLifecycle.Observer() {
-                @Override
-                public void onStartedGoingToSleep() {
-                    animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-                }
-            };
-
-    private final class DialogOutlineProvider extends ViewOutlineProvider {
-
-        float mY;
-
-        @Override
-        public void getOutline(View view, Outline outline) {
-            outline.setRoundRect(
-                    0 /* left */,
-                    (int) mY, /* top */
-                    mDialog.getWidth() /* right */,
-                    mDialog.getBottom(), /* bottom */
-                    getResources().getDimension(R.dimen.biometric_dialog_corner_size));
-        }
-
-        int calculateSmall() {
-            final float padding = Utils.dpToPixels(mContext, IMPLICIT_Y_PADDING);
-            return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
-        }
-
-        void setOutlineY(float y) {
-            mY = y;
-        }
-    }
-
-    protected Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case MSG_RESET_MESSAGE:
-                    handleResetMessage();
-                    break;
-                default:
-                    Log.e(TAG, "Unhandled message: " + msg.what);
-                    break;
-            }
-        }
-    };
-
-    /**
-     * Builds the dialog with specified parameters.
-     */
-    public static class Builder {
-        public static final int TYPE_FINGERPRINT = BiometricAuthenticator.TYPE_FINGERPRINT;
-        public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE;
-
-        private Context mContext;
-        private DialogViewCallback mCallback;
-        private Bundle mBundle;
-        private boolean mRequireConfirmation;
-        private int mUserId;
-        private String mOpPackageName;
-
-        public Builder(Context context) {
-            mContext = context;
-        }
-
-        public Builder setCallback(DialogViewCallback callback) {
-            mCallback = callback;
-            return this;
-        }
-
-        public Builder setBiometricPromptBundle(Bundle bundle) {
-            mBundle = bundle;
-            return this;
-        }
-
-        public Builder setRequireConfirmation(boolean requireConfirmation) {
-            mRequireConfirmation = requireConfirmation;
-            return this;
-        }
-
-        public Builder setUserId(int userId) {
-            mUserId = userId;
-            return this;
-        }
-
-        public Builder setOpPackageName(String opPackageName) {
-            mOpPackageName = opPackageName;
-            return this;
-        }
-
-        public BiometricDialogView build(int type) {
-            return build(type, new Injector());
-        }
-
-        public BiometricDialogView build(int type, Injector injector) {
-            BiometricDialogView dialog;
-            if (type == TYPE_FINGERPRINT) {
-                dialog = new FingerprintDialogView(mContext, mCallback, injector);
-            } else if (type == TYPE_FACE) {
-                dialog = new FaceDialogView(mContext, mCallback, injector);
-            } else {
-                return null;
-            }
-            dialog.setBundle(mBundle);
-            dialog.setRequireConfirmation(mRequireConfirmation);
-            dialog.setUserId(mUserId);
-            dialog.setOpPackageName(mOpPackageName);
-            return dialog;
-        }
-    }
-
-    public static class Injector {
-        public WakefulnessLifecycle getWakefulnessLifecycle() {
-            return Dependency.get(WakefulnessLifecycle.class);
-        }
-    }
-
-    protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) {
-        super(context);
-        mWakefulnessLifecycle = injector.getWakefulnessLifecycle();
-
-        mCallback = callback;
-        mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
-        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-        mUserManager = mContext.getSystemService(UserManager.class);
-        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
-        mAnimationTranslationOffset = getResources()
-                .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
-        mErrorColor = getResources().getColor(R.color.biometric_dialog_error);
-        mTextColor = getResources().getColor(R.color.biometric_dialog_gray);
-
-        DisplayMetrics metrics = new DisplayMetrics();
-        mWindowManager.getDefaultDisplay().getMetrics(metrics);
-        mDialogWidth = Math.min(metrics.widthPixels, metrics.heightPixels);
-
-        // Create the dialog
-        LayoutInflater factory = LayoutInflater.from(getContext());
-        mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
-        addView(mLayout);
-
-        mLayout.setOnKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                if (keyCode != KeyEvent.KEYCODE_BACK) {
-                    return false;
-                }
-                if (event.getAction() == KeyEvent.ACTION_UP) {
-                    animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-                }
-                return true;
-            }
-        });
-
-        final View space = mLayout.findViewById(R.id.space);
-        final View leftSpace = mLayout.findViewById(R.id.left_space);
-        final View rightSpace = mLayout.findViewById(R.id.right_space);
-
-        mDialog = mLayout.findViewById(R.id.dialog);
-        mTitleText = mLayout.findViewById(R.id.title);
-        mSubtitleText = mLayout.findViewById(R.id.subtitle);
-        mDescriptionText = mLayout.findViewById(R.id.description);
-        mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
-        mErrorText = mLayout.findViewById(R.id.error);
-        mNegativeButton = mLayout.findViewById(R.id.button2);
-        mPositiveButton = mLayout.findViewById(R.id.button1);
-        mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
-
-        mBiometricIcon.setContentDescription(
-                getResources().getString(getIconDescriptionResourceId()));
-
-        setDismissesDialog(space);
-        setDismissesDialog(leftSpace);
-        setDismissesDialog(rightSpace);
-
-        mNegativeButton.setOnClickListener((View v) -> {
-            if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) {
-                animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-            } else {
-                animateAway(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
-            }
-        });
-
-        mPositiveButton.setOnClickListener((View v) -> {
-            updateState(STATE_AUTHENTICATED);
-            mHandler.postDelayed(() -> {
-                animateAway(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
-            }, getDelayAfterAuthenticatedDurationMs());
-        });
-
-        mTryAgainButton.setOnClickListener((View v) -> {
-            handleResetMessage();
-            updateState(STATE_AUTHENTICATING);
-            showTryAgainButton(false /* show */);
-
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(false);
-
-            mCallback.onTryAgainPressed();
-        });
-
-        // Must set these in order for the back button events to be received.
-        mLayout.setFocusableInTouchMode(true);
-        mLayout.requestFocus();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
-
-        final ImageView backgroundView = mLayout.findViewById(R.id.background);
-        if (mUserManager.isManagedProfile(mUserId)) {
-            final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
-                    mContext.getTheme());
-            image.setColorFilter(mDevicePolicyManager.getOrganizationColorForUser(mUserId),
-                    PorterDuff.Mode.DARKEN);
-            backgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
-            backgroundView.setImageDrawable(image);
-        } else {
-            backgroundView.setImageDrawable(null);
-            backgroundView.setBackgroundColor(R.color.biometric_dialog_dim_color);
-        }
-
-        mNegativeButton.setVisibility(View.VISIBLE);
-        mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
-
-        if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) {
-            mDialog.getLayoutParams().width = (int) mDialogWidth;
-        }
-
-        if (mRestoredState == null) {
-            updateState(STATE_AUTHENTICATING);
-            final int hint = getHintStringResourceId();
-            if (hint != 0) {
-                mErrorText.setText(hint);
-                mErrorText.setContentDescription(mContext.getString(hint));
-                mErrorText.setVisibility(View.VISIBLE);
-            } else {
-                mErrorText.setVisibility(View.INVISIBLE);
-            }
-            announceAccessibilityEvent();
-        } else {
-            updateState(mState);
-        }
-
-        CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
-
-        mTitleText.setVisibility(View.VISIBLE);
-        mTitleText.setText(titleText);
-
-        final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
-        if (TextUtils.isEmpty(subtitleText)) {
-            mSubtitleText.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        } else {
-            mSubtitleText.setVisibility(View.VISIBLE);
-            mSubtitleText.setText(subtitleText);
-        }
-
-        final CharSequence descriptionText =
-                mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
-        if (TextUtils.isEmpty(descriptionText)) {
-            mDescriptionText.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        } else {
-            mDescriptionText.setVisibility(View.VISIBLE);
-            mDescriptionText.setText(descriptionText);
-        }
-
-        if (requiresConfirmation() && mRestoredState == null) {
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(false);
-        }
-
-        if (mWasForceRemoved || mSkipIntro) {
-            // Show the dialog immediately
-            mLayout.animate().cancel();
-            mDialog.animate().cancel();
-            mDialog.setAlpha(1.0f);
-            mDialog.setTranslationY(0);
-            mLayout.setAlpha(1.0f);
-            mCompletedAnimatingIn = true;
-        } else {
-            // Dim the background and slide the dialog up
-            mDialog.setTranslationY(mAnimationTranslationOffset);
-            mLayout.setAlpha(0f);
-            postOnAnimation(mShowAnimationRunnable);
-        }
-        mWasForceRemoved = false;
-        mSkipIntro = false;
-    }
-
-    /**
-     * Do small/big layout here instead of onAttachedToWindow, since:
-     * 1) We need the big layout to be measured, etc for small -> big animation
-     * 2) We need the dialog measurements to know where to move the biometric icon to
-     *
-     * BiometricDialogView already sets the views to their default big state, so here we only
-     * need to hide the ones that are unnecessary.
-     */
-    @Override
-    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        if (mIconOriginalY == 0) {
-            mIconOriginalY = mBiometricIcon.getY();
-        }
-
-        // UNKNOWN means size hasn't been set yet. First time we create the dialog.
-        // onLayout can happen when visibility of views change (during animation, etc).
-        if (getSize() != SIZE_UNKNOWN) {
-            // Probably not the cleanest way to do this, but since dialog is big by default,
-            // and small dialogs can persist across orientation changes, we need to set it to
-            // small size here again.
-            if (getSize() == SIZE_SMALL) {
-                updateSize(SIZE_SMALL);
-            }
-            return;
-        }
-
-        // If we don't require confirmation, show the small dialog first (until errors occur).
-        if (!requiresConfirmation() && supportsSmallDialog()) {
-            updateSize(SIZE_SMALL);
-        } else {
-            updateSize(SIZE_BIG);
-        }
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
-    }
-
-    @VisibleForTesting
-    void updateSize(@DialogSize int newSize) {
-        final float padding = Utils.dpToPixels(mContext, IMPLICIT_Y_PADDING);
-        final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
-
-        if (newSize == SIZE_SMALL) {
-            if (!supportsSmallDialog()) {
-                Log.e(TAG, "Small dialog unsupported");
-                return;
-            }
-
-            // These fields are required and/or always hold a spot on the UI, so should be set to
-            // INVISIBLE so they keep their position
-            mTitleText.setVisibility(View.INVISIBLE);
-            mErrorText.setVisibility(View.INVISIBLE);
-            mNegativeButton.setVisibility(View.INVISIBLE);
-
-            // These fields are optional, so set them to gone or invisible depending on their
-            // usage. If they're empty, they're already set to GONE in BiometricDialogView.
-            if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                mSubtitleText.setVisibility(View.INVISIBLE);
-            }
-            if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                mDescriptionText.setVisibility(View.INVISIBLE);
-            }
-
-            // Move the biometric icon to the small spot
-            mBiometricIcon.setY(iconSmallPositionY);
-
-            // Clip the dialog to the small size
-            mDialog.setOutlineProvider(mOutlineProvider);
-            mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
-
-            mDialog.setClipToOutline(true);
-            mDialog.invalidateOutline();
-
-            mSize = newSize;
-            announceAccessibilityEvent();
-        } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
-            mSize = SIZE_GROWING;
-
-            // Animate the outline
-            final ValueAnimator outlineAnimator =
-                    ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
-            outlineAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mOutlineProvider.setOutlineY(y);
-                mDialog.invalidateOutline();
-            });
-
-            // Animate the icon back to original big position
-            final ValueAnimator iconAnimator =
-                    ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
-            iconAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mBiometricIcon.setY(y);
-            });
-
-            // Animate the error text so it slides up with the icon
-            final ValueAnimator textSlideAnimator =
-                    ValueAnimator.ofFloat(Utils.dpToPixels(mContext, TEXT_ANIMATE_DISTANCE), 0);
-            textSlideAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mErrorText.setTranslationY(y);
-            });
-
-            // Opacity animator for things that should fade in (title, subtitle, details, negative
-            // button)
-            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
-            opacityAnimator.addUpdateListener((animation) -> {
-                final float opacity = (float) animation.getAnimatedValue();
-
-                // These fields are required and/or always hold a spot on the UI
-                mTitleText.setAlpha(opacity);
-                mErrorText.setAlpha(opacity);
-                mNegativeButton.setAlpha(opacity);
-                mTryAgainButton.setAlpha(opacity);
-
-                // These fields are optional, so only animate them if they're supposed to be showing
-                if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                    mSubtitleText.setAlpha(opacity);
-                }
-                if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                    mDescriptionText.setAlpha(opacity);
-                }
-            });
-
-            // Choreograph together
-            final AnimatorSet as = new AnimatorSet();
-            as.setDuration(GROW_DURATION);
-            as.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    super.onAnimationStart(animation);
-                    // Set the visibility of opacity-animating views back to VISIBLE
-                    mTitleText.setVisibility(View.VISIBLE);
-                    mErrorText.setVisibility(View.VISIBLE);
-                    mNegativeButton.setVisibility(View.VISIBLE);
-                    mTryAgainButton.setVisibility(View.VISIBLE);
-
-                    if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                        mSubtitleText.setVisibility(View.VISIBLE);
-                    }
-                    if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                        mDescriptionText.setVisibility(View.VISIBLE);
-                    }
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    mSize = SIZE_BIG;
-                }
-            });
-            as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
-                    .with(textSlideAnimator);
-            as.start();
-        } else if (mSize == SIZE_BIG) {
-            mDialog.setClipToOutline(false);
-            mDialog.invalidateOutline();
-
-            mBiometricIcon.setY(mIconOriginalY);
-
-            mSize = newSize;
-        }
-    }
-
-    private void setDismissesDialog(View v) {
-        v.setClickable(true);
-        v.setOnClickListener(v1 -> {
-            if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
-                animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-            }
-        });
-    }
-
-    private void animateAway(@DialogViewCallback.DismissedReason int reason) {
-        animateAway(true /* sendReason */, reason);
-    }
-
-    /**
-     * Animate the dialog away
-     * @param reason one of the {@link DialogViewCallback} codes
-     */
-    private void animateAway(boolean sendReason, @DialogViewCallback.DismissedReason int reason) {
-        if (!mCompletedAnimatingIn) {
-            Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
-            mPendingDismissDialog = true;
-            return;
-        }
-
-        // This is where final cleanup should occur.
-        final Runnable endActionRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mWindowManager.removeView(BiometricDialogView.this);
-                // Set the icons / text back to normal state
-                handleResetMessage();
-                showTryAgainButton(false /* show */);
-                updateState(STATE_IDLE);
-                if (sendReason) {
-                    mCallback.onDismissed(reason);
-                }
-            }
-        };
-
-        postOnAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mLayout.animate()
-                        .alpha(0f)
-                        .setDuration(ANIMATION_DURATION_AWAY)
-                        .setInterpolator(mLinearOutSlowIn)
-                        .withLayer()
-                        .start();
-                mDialog.animate()
-                        .translationY(mAnimationTranslationOffset)
-                        .setDuration(ANIMATION_DURATION_AWAY)
-                        .setInterpolator(mLinearOutSlowIn)
-                        .withLayer()
-                        .withEndAction(endActionRunnable)
-                        .start();
-            }
-        });
-    }
-
-    /**
-     * Skip the intro animation
-     */
-    private void setSkipIntro(boolean skip) {
-        mSkipIntro = skip;
-    }
-
-    private void setBundle(Bundle bundle) {
-        mBundle = bundle;
-    }
-
-    private void setRequireConfirmation(boolean requireConfirmation) {
-        mRequireConfirmation = requireConfirmation;
-    }
-
-    protected boolean requiresConfirmation() {
-        return mRequireConfirmation;
-    }
-
-    private void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    private void setOpPackageName(String opPackageName) {
-        mOpPackageName = opPackageName;
-    }
-
-    // Shows an error/help message
-    protected void showTemporaryMessage(String message) {
-        mHandler.removeMessages(MSG_RESET_MESSAGE);
-        mErrorText.setText(message);
-        mErrorText.setTextColor(mErrorColor);
-        mErrorText.setContentDescription(message);
-        mErrorText.setVisibility(View.VISIBLE);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
-                BiometricPrompt.HIDE_DIALOG_DELAY);
-    }
-
-    @Override
-    public void show(WindowManager wm, boolean skipIntroAnimation) {
-        setSkipIntro(skipIntroAnimation);
-        wm.addView(this, getLayoutParams(mWindowToken));
-    }
-
-    /**
-     * Force remove the window, cancelling any animation that's happening. This should only be
-     * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method
-     * will cause the dialog to show without an animation the next time it's attached.
-     */
-    @Override
-    public void dismissWithoutCallback(boolean animate) {
-        if (animate) {
-            animateAway(false /* sendReason */, 0 /* reason */);
-        } else {
-            mLayout.animate().cancel();
-            mDialog.animate().cancel();
-            mWindowManager.removeView(BiometricDialogView.this);
-            mWasForceRemoved = true;
-        }
-    }
-
-    @Override
-    public void dismissFromSystemServer() {
-        animateAway(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
-    }
-
-    @Override
-    public void onAuthenticationSucceeded() {
-        announceForAccessibility(getResources().getText(getAuthenticatedAccessibilityResourceId()));
-
-        if (requiresConfirmation()) {
-            updateState(STATE_PENDING_CONFIRMATION);
-        } else {
-            mHandler.postDelayed(() -> {
-                animateAway(DialogViewCallback.DISMISSED_AUTHENTICATED);
-            }, getDelayAfterAuthenticatedDurationMs());
-
-            updateState(STATE_AUTHENTICATED);
-        }
-    }
-
-
-    @Override
-    public void onAuthenticationFailed(String message) {
-        updateState(STATE_ERROR);
-        showTemporaryMessage(message);
-    }
-
-    /**
-     * Transient help message (acquire) is received, dialog stays showing. Sensor stays in
-     * "authenticating" state.
-     * @param message
-     */
-    @Override
-    public void onHelp(String message) {
-        updateState(STATE_ERROR);
-        showTemporaryMessage(message);
-    }
-
-    /**
-     * Hard error is received, dialog will be dismissed soon.
-     * @param error
-     */
-    @Override
-    public void onError(String error) {
-        // All error messages will cause the dialog to go from small -> big. Error messages
-        // are messages such as lockout, auth failed, etc.
-        if (mSize == SIZE_SMALL) {
-            updateSize(SIZE_BIG);
-        }
-
-        updateState(STATE_ERROR);
-        showTemporaryMessage(error);
-        showTryAgainButton(false /* show */);
-
-        mHandler.postDelayed(() -> {
-            animateAway(DialogViewCallback.DISMISSED_ERROR);
-        }, BiometricPrompt.HIDE_DIALOG_DELAY);
-    }
-
-
-    @Override
-    public void onSaveState(Bundle bundle) {
-        bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
-        bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
-        bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled());
-        bundle.putInt(KEY_STATE, mState);
-        bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility());
-        bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText());
-        bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE));
-        bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor());
-        bundle.putInt(KEY_DIALOG_SIZE, mSize);
-    }
-
-    @Override
-    public void restoreState(Bundle bundle) {
-        mRestoredState = bundle;
-
-        // Keep in mind that this happens before onAttachedToWindow()
-        mSize = bundle.getInt(KEY_DIALOG_SIZE);
-
-        final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY);
-        mTryAgainButton.setVisibility(tryAgainVisibility);
-        final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY);
-        mPositiveButton.setVisibility(confirmVisibility);
-        final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED);
-        mPositiveButton.setEnabled(confirmEnabled);
-        mState = bundle.getInt(KEY_STATE);
-        mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
-        mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
-        final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY);
-        mErrorText.setVisibility(errorTextVisibility);
-        if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE
-                || confirmVisibility == View.INVISIBLE) {
-            announceAccessibilityEvent();
-        }
-        mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
-        if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) {
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
-                    BiometricPrompt.HIDE_DIALOG_DELAY);
-        }
-    }
-
-    @Override
-    public String getOpPackageName() {
-        return mOpPackageName;
-    }
-
-    protected void updateState(int newState) {
-        if (newState == STATE_PENDING_CONFIRMATION) {
-            mHandler.removeMessages(MSG_RESET_MESSAGE);
-            mErrorText.setTextColor(mTextColor);
-            mErrorText.setText(R.string.biometric_dialog_tap_confirm);
-            mErrorText.setContentDescription(
-                    getResources().getString(R.string.biometric_dialog_tap_confirm));
-            mErrorText.setVisibility(View.VISIBLE);
-            announceAccessibilityEvent();
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(true);
-        } else if (newState == STATE_AUTHENTICATED) {
-            mPositiveButton.setVisibility(View.GONE);
-            mNegativeButton.setVisibility(View.GONE);
-            mErrorText.setVisibility(View.INVISIBLE);
-            announceAccessibilityEvent();
-        }
-
-        if (newState == STATE_PENDING_CONFIRMATION || newState == STATE_AUTHENTICATED) {
-            mNegativeButton.setText(R.string.cancel);
-            mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
-        }
-
-        updateIcon(mState, newState);
-        mState = newState;
-    }
-
-    protected @DialogSize int getSize() {
-        return mSize;
-    }
-
-    protected void showTryAgainButton(boolean show) {
-        if (show && getSize() == SIZE_SMALL) {
-            // Do not call super, we will nicely animate the alpha together with the rest
-            // of the elements in here.
-            updateSize(SIZE_BIG);
-        } else {
-            if (show) {
-                mTryAgainButton.setVisibility(View.VISIBLE);
-            } else {
-                mTryAgainButton.setVisibility(View.GONE);
-                announceAccessibilityEvent();
-            }
-        }
-
-        if (show) {
-            mPositiveButton.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        }
-    }
-
-    protected void onDialogAnimatedIn() {
-        mCompletedAnimatingIn = true;
-
-        if (mPendingDismissDialog) {
-            Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
-            animateAway(false /* sendReason */, 0);
-            mPendingDismissDialog = false;
-        }
-    }
-
-    /**
-     * @param windowToken token for the window
-     * @return
-     */
-    public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
-                PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-        lp.setTitle("BiometricDialogView");
-        lp.token = windowToken;
-        return lp;
-    }
-
-    // Every time a view becomes invisible we need to announce an accessibility event.
-    // This is due to an issue in the framework, b/132298701 recommended this workaround.
-    protected void announceAccessibilityEvent() {
-        if (!mAccessibilityManager.isEnabled()) {
-            return;
-        }
-        AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
-        mDialog.sendAccessibilityEventUnchecked(event);
-        mDialog.notifySubtreeAccessibilityStateChanged(mDialog, mDialog,
-                CONTENT_CHANGE_TYPE_SUBTREE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
deleted file mode 100644
index 9e4fe24..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
-
-/**
- * This class loads the view for the system-provided dialog. The view consists of:
- * Application Icon, Title, Subtitle, Description, Biometric Icon, Error/Help message area,
- * and positive/negative buttons.
- */
-public class FaceDialogView extends BiometricDialogView {
-
-    private static final String TAG = "FaceDialogView";
-
-    private static final String KEY_DIALOG_ANIMATED_IN = "key_dialog_animated_in";
-
-    private static final int HIDE_DIALOG_DELAY = 500; // ms
-
-    private IconController mIconController;
-    private boolean mDialogAnimatedIn;
-
-    /**
-     * Class that handles the biometric icon animations.
-     */
-    private final class IconController extends Animatable2.AnimationCallback {
-
-        private boolean mLastPulseDirection; // false = dark to light, true = light to dark
-
-        int mState;
-
-        IconController() {
-            mState = STATE_IDLE;
-        }
-
-        public void animateOnce(int iconRes) {
-            animateIcon(iconRes, false);
-        }
-
-        public void showStatic(int iconRes) {
-            mBiometricIcon.setImageDrawable(mContext.getDrawable(iconRes));
-        }
-
-        public void startPulsing() {
-            mLastPulseDirection = false;
-            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
-        }
-
-        public void showIcon(int iconRes) {
-            final Drawable drawable = mContext.getDrawable(iconRes);
-            mBiometricIcon.setImageDrawable(drawable);
-        }
-
-        private void animateIcon(int iconRes, boolean repeat) {
-            final AnimatedVectorDrawable icon =
-                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
-            mBiometricIcon.setImageDrawable(icon);
-            icon.forceAnimationOnUI();
-            if (repeat) {
-                icon.registerAnimationCallback(this);
-            }
-            icon.start();
-        }
-
-        private void pulseInNextDirection() {
-            int iconRes = mLastPulseDirection ? R.drawable.face_dialog_pulse_dark_to_light
-                    : R.drawable.face_dialog_pulse_light_to_dark;
-            animateIcon(iconRes, true /* repeat */);
-            mLastPulseDirection = !mLastPulseDirection;
-        }
-
-        @Override
-        public void onAnimationEnd(Drawable drawable) {
-            super.onAnimationEnd(drawable);
-
-            if (mState == STATE_AUTHENTICATING) {
-                // Still authenticating, pulse the icon
-                pulseInNextDirection();
-            }
-        }
-    }
-
-    private final Runnable mErrorToIdleAnimationRunnable = () -> {
-        updateState(STATE_IDLE);
-        mErrorText.setVisibility(View.INVISIBLE);
-        announceAccessibilityEvent();
-    };
-
-    protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) {
-        super(context, callback, injector);
-        mIconController = new IconController();
-    }
-
-    @Override
-    public void onSaveState(Bundle bundle) {
-        super.onSaveState(bundle);
-        bundle.putBoolean(KEY_DIALOG_ANIMATED_IN, mDialogAnimatedIn);
-    }
-
-
-    @Override
-    protected void handleResetMessage() {
-        mErrorText.setTextColor(mTextColor);
-        mErrorText.setVisibility(View.INVISIBLE);
-        announceAccessibilityEvent();
-    }
-
-    @Override
-    public void restoreState(Bundle bundle) {
-        super.restoreState(bundle);
-        mDialogAnimatedIn = bundle.getBoolean(KEY_DIALOG_ANIMATED_IN);
-    }
-
-    @Override
-    public void onAuthenticationFailed(String message) {
-        super.onAuthenticationFailed(message);
-        showTryAgainButton(true);
-    }
-
-    @Override
-    protected int getHintStringResourceId() {
-        return 0;
-    }
-
-    @Override
-    protected int getAuthenticatedAccessibilityResourceId() {
-        if (mRequireConfirmation) {
-            return com.android.internal.R.string.face_authenticated_confirmation_required;
-        } else {
-            return com.android.internal.R.string.face_authenticated_no_confirmation_required;
-        }
-    }
-
-    @Override
-    protected int getIconDescriptionResourceId() {
-        return R.string.accessibility_face_dialog_face_icon;
-    }
-
-    @Override
-    protected void updateIcon(int oldState, int newState) {
-        mIconController.mState = newState;
-
-        if (newState == STATE_AUTHENTICATING) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            if (mDialogAnimatedIn) {
-                mIconController.startPulsing();
-            } else {
-                mIconController.showIcon(R.drawable.face_dialog_pulse_dark_to_light);
-            }
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating));
-        } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_confirmed));
-        } else if (oldState == STATE_ERROR && newState == STATE_IDLE) {
-            mIconController.animateOnce(R.drawable.face_dialog_error_to_idle);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_idle));
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_ERROR) {
-            // It's easier to only check newState and gate showing the animation on the
-            // mErrorToIdleAnimationRunnable as a proxy, than add a ton of extra state. For example,
-            // we may go from error -> error due to configuration change which is valid and we
-            // should show the animation, or we can go from error -> error by receiving repeated
-            // acquire messages in which case we do not want to repeatedly start the animation.
-            if (!mHandler.hasCallbacks(mErrorToIdleAnimationRunnable)) {
-                mIconController.animateOnce(R.drawable.face_dialog_dark_to_error);
-                mHandler.postDelayed(mErrorToIdleAnimationRunnable,
-                        BiometricPrompt.HIDE_DIALOG_DELAY);
-            }
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_PENDING_CONFIRMATION) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_IDLE) {
-            mIconController.showStatic(R.drawable.face_dialog_idle_static);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_idle));
-        } else {
-            Log.w(TAG, "Unknown animation from " + oldState + " -> " + newState);
-        }
-
-        // Note that this must be after the newState == STATE_ERROR check above since this affects
-        // the logic.
-        if (oldState == STATE_ERROR && newState == STATE_ERROR) {
-            // Keep the error icon and text around for a while longer if we keep receiving
-            // STATE_ERROR
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
-        }
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return true;
-    }
-
-    @Override
-    public void onDialogAnimatedIn() {
-        super.onDialogAnimatedIn();
-        mDialogAnimatedIn = true;
-        mIconController.startPulsing();
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return HIDE_DIALOG_DELAY;
-    }
-
-    @Override
-    protected boolean shouldGrayAreaDismissDialog() {
-        if (getSize() == SIZE_SMALL) {
-            return false;
-        }
-        return true;
-    }
-
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
deleted file mode 100644
index 2925880..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
-
-/**
- * This class loads the view for the system-provided dialog. The view consists of:
- * Application Icon, Title, Subtitle, Description, Biometric Icon, Error/Help message area,
- * and positive/negative buttons.
- */
-public class FingerprintDialogView extends BiometricDialogView {
-
-    private static final String TAG = "FingerprintDialogView";
-
-    protected FingerprintDialogView(Context context, DialogViewCallback callback,
-            Injector injector) {
-        super(context, callback, injector);
-    }
-
-    @Override
-    protected void handleResetMessage() {
-        updateState(STATE_AUTHENTICATING);
-        mErrorText.setText(getHintStringResourceId());
-        mErrorText.setTextColor(mTextColor);
-    }
-
-    @Override
-    protected int getHintStringResourceId() {
-        return R.string.fingerprint_dialog_touch_sensor;
-    }
-
-    @Override
-    protected int getAuthenticatedAccessibilityResourceId() {
-        return com.android.internal.R.string.fingerprint_authenticated;
-    }
-
-    @Override
-    protected int getIconDescriptionResourceId() {
-        return R.string.accessibility_fingerprint_dialog_fingerprint_icon;
-    }
-
-    @Override
-    protected void updateIcon(int lastState, int newState) {
-        final Drawable icon = getAnimationForTransition(lastState, newState);
-        if (icon == null) {
-            Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
-            return;
-        }
-
-        final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
-                ? (AnimatedVectorDrawable) icon
-                : null;
-
-        mBiometricIcon.setImageDrawable(icon);
-
-        if (animation != null && shouldAnimateForTransition(lastState, newState)) {
-            animation.forceAnimationOnUI();
-            animation.start();
-        }
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return false;
-    }
-
-    protected boolean shouldAnimateForTransition(int oldState, int newState) {
-        if (newState == STATE_ERROR) {
-            return true;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) {
-            return true;
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            return false;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            return false;
-        } else if (newState == STATE_AUTHENTICATING) {
-            return false;
-        }
-        return false;
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return 0;
-    }
-
-    @Override
-    protected boolean shouldGrayAreaDismissDialog() {
-        // Fingerprint dialog always dismisses when region outside the dialog is tapped
-        return true;
-    }
-
-    protected Drawable getAnimationForTransition(int oldState, int newState) {
-        int iconRes;
-        if (newState == STATE_ERROR) {
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) {
-            iconRes = R.drawable.fingerprint_dialog_error_to_fp;
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (newState == STATE_AUTHENTICATING) {
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else {
-            return null;
-        }
-        return mContext.getDrawable(iconRes);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java
deleted file mode 100644
index 028b1aa..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java
+++ /dev/null
@@ -1,32 +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.biometrics.ui;
-
-import android.content.Context;
-import android.util.DisplayMetrics;
-
-public class Utils {
-    static float dpToPixels(Context context, float dp) {
-        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-
-    static float pixelsToDp(Context context, float pixels) {
-        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
deleted file mode 100644
index 74ad0fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.bubbles;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-// XXX: Mostly opied from launcher code / can we share?
-/**
- * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
- */
-public class BadgeRenderer {
-
-    private static final String TAG = "BadgeRenderer";
-
-    /** The badge sizes are defined as percentages of the app icon size. */
-    private static final float SIZE_PERCENTAGE = 0.38f;
-
-    /** Extra scale down of the dot. */
-    private static final float DOT_SCALE = 0.6f;
-
-    private final float mDotCenterOffset;
-    private final float mCircleRadius;
-    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
-    public BadgeRenderer(Context context) {
-        mDotCenterOffset = getDotCenterOffset(context);
-        mCircleRadius = getDotRadius(mDotCenterOffset);
-    }
-
-    /** Space between the center of the dot and the top or left of the bubble stack. */
-    static float getDotCenterOffset(Context context) {
-        final int iconSizePx =
-                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        return SIZE_PERCENTAGE * iconSizePx;
-    }
-
-    static float getDotRadius(float dotCenterOffset) {
-        int size = (int) (DOT_SCALE * dotCenterOffset);
-        return size / 2f;
-    }
-
-    /**
-     * Draw a circle in the top right corner of the given bounds.
-     *
-     * @param color The color (based on the icon) to use for the badge.
-     * @param iconBounds The bounds of the icon being badged.
-     * @param badgeScale The progress of the animation, from 0 to 1.
-     * @param spaceForOffset How much space to offset the badge up and to the left or right.
-     * @param onLeft Whether the badge should be draw on left or right side.
-     */
-    public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
-            Point spaceForOffset, boolean onLeft) {
-        if (iconBounds == null) {
-            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
-            return;
-        }
-        canvas.save();
-        // We draw the badge relative to its center.
-        int x = onLeft ? iconBounds.left : iconBounds.right;
-        float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
-        float badgeCenterX = x + offset;
-        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
-
-        canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
-
-        canvas.scale(badgeScale, badgeScale);
-        mCirclePaint.setColor(color);
-        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
-        canvas.restore();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 783780f..c0053d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -18,12 +18,13 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Point;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.widget.ImageView;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.systemui.R;
 
 /**
@@ -31,16 +32,19 @@
  */
 public class BadgedImageView extends ImageView {
 
-    private BadgeRenderer mDotRenderer;
-    private int mIconSize;
     private Rect mTempBounds = new Rect();
-    private Point mTempPoint = new Point();
 
+    private DotRenderer mDotRenderer;
+    private DotRenderer.DrawParams mDrawParams;
+    private int mIconBitmapSize;
+    private int mDotColor;
     private float mDotScale = 0f;
-    private int mUpdateDotColor;
-    private boolean mShowUpdateDot;
+    private boolean mShowDot;
     private boolean mOnLeft;
 
+    /** Same as value in Launcher3 IconShape */
+    static final int DEFAULT_PATH_SIZE = 100;
+
     public BadgedImageView(Context context) {
         this(context, null);
     }
@@ -56,69 +60,100 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mDotRenderer = new BadgeRenderer(getContext());
+        mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mDrawParams = new DotRenderer.DrawParams();
 
         TypedArray ta = context.obtainStyledAttributes(
-                new int[] {android.R.attr.colorBackgroundFloating});
+                new int[]{android.R.attr.colorBackgroundFloating});
         ta.recycle();
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mShowUpdateDot) {
-            getDrawingRect(mTempBounds);
-            mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
-            mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
-                    mOnLeft);
+        if (!mShowDot) {
+            return;
         }
+        getDrawingRect(mTempBounds);
+
+        mDrawParams.color = mDotColor;
+        mDrawParams.iconBounds = mTempBounds;
+        mDrawParams.leftAlign = mOnLeft;
+        mDrawParams.scale = mDotScale;
+
+        if (mDotRenderer == null) {
+            Path circlePath = new Path();
+            float radius = DEFAULT_PATH_SIZE * 0.5f;
+            circlePath.addCircle(radius /* x */, radius /* y */, radius, Path.Direction.CW);
+            mDotRenderer = new DotRenderer(mIconBitmapSize, circlePath, DEFAULT_PATH_SIZE);
+        }
+        mDotRenderer.draw(canvas, mDrawParams);
     }
 
     /**
      * Set whether the dot should appear on left or right side of the view.
      */
-    public void setDotPosition(boolean onLeft) {
+    void setDotOnLeft(boolean onLeft) {
         mOnLeft = onLeft;
         invalidate();
     }
 
-    public boolean getDotPosition() {
+    boolean getDotOnLeft() {
         return mOnLeft;
     }
 
     /**
      * Set whether the dot should show or not.
      */
-    public void setShowDot(boolean showBadge) {
-        mShowUpdateDot = showBadge;
+    void setShowDot(boolean showDot) {
+        mShowDot = showDot;
         invalidate();
     }
 
     /**
      * @return whether the dot is being displayed.
      */
-    public boolean isShowingDot() {
-        return mShowUpdateDot;
+    boolean isShowingDot() {
+        return mShowDot;
     }
 
     /**
      * The colour to use for the dot.
      */
     public void setDotColor(int color) {
-        mUpdateDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        invalidate();
+    }
+
+    /**
+     * @param iconPath The new icon path to use when calculating dot position.
+     */
+    public void drawDot(Path iconPath) {
+        mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
         invalidate();
     }
 
     /**
      * How big the dot should be, fraction from 0 to 1.
      */
-    public void setDotScale(float fraction) {
+    void setDotScale(float fraction) {
         mDotScale = fraction;
         invalidate();
     }
 
-    public float getDotScale() {
-        return mDotScale;
+    /**
+     * Return dot position relative to bubble view container bounds.
+     */
+    float[] getDotCenter() {
+        float[] dotPosition;
+        if (mOnLeft) {
+            dotPosition = mDotRenderer.getLeftDotPosition();
+        } else {
+            dotPosition =  mDotRenderer.getRightDotPosition();
+        }
+        getDrawingRect(mTempBounds);
+        float dotCenterX = mTempBounds.width() * dotPosition[0];
+        float dotCenterY = mTempBounds.height() * dotPosition[1];
+        return new float[]{dotCenterX, dotCenterY};
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 5c6c397..c3cee35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -20,38 +20,67 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
 import java.util.Objects;
 
 /**
  * Encapsulates the data and UI elements of a bubble.
  */
 class Bubble {
-
-    private static final boolean DEBUG = false;
     private static final String TAG = "Bubble";
 
+    private NotificationEntry mEntry;
     private final String mKey;
     private final String mGroupId;
     private String mAppName;
-    private final BubbleExpandedView.OnBubbleBlockedListener mListener;
+    private Drawable mUserBadgedAppIcon;
 
     private boolean mInflated;
-    public NotificationEntry entry;
-    BubbleView iconView;
-    BubbleExpandedView expandedView;
+    private BubbleView mIconView;
+    private BubbleExpandedView mExpandedView;
+
     private long mLastUpdated;
     private long mLastAccessed;
-    private PackageManager mPm;
+    private boolean mIsRemoved;
+
+    /**
+     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+     *
+     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+     * expanded</p>
+     */
+    private boolean mShowInShadeWhenBubble = true;
+
+    /**
+     * Whether the bubble should show a dot for the notification indicating updated content.
+     */
+    private boolean mShowBubbleUpdateDot = true;
+
+    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
+    private boolean mSuppressFlyout;
 
     public static String groupId(NotificationEntry entry) {
         UserHandle user = entry.notification.getUser();
@@ -61,31 +90,27 @@
     /** Used in tests when no UI is required. */
     @VisibleForTesting(visibility = PRIVATE)
     Bubble(Context context, NotificationEntry e) {
-        this (context, e, null);
-    }
-
-    Bubble(Context context, NotificationEntry e,
-            BubbleExpandedView.OnBubbleBlockedListener listener) {
-        entry = e;
+        mEntry = e;
         mKey = e.key;
         mLastUpdated = e.notification.getPostTime();
         mGroupId = groupId(e);
-        mListener = listener;
 
-        mPm = context.getPackageManager();
+        PackageManager pm = context.getPackageManager();
         ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                entry.notification.getPackageName(),
+            info = pm.getApplicationInfo(
+                mEntry.notification.getPackageName(),
                 PackageManager.MATCH_UNINSTALLED_PACKAGES
                     | PackageManager.MATCH_DISABLED_COMPONENTS
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
-                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+                mAppName = String.valueOf(pm.getApplicationLabel(info));
             }
+            Drawable appIcon = pm.getApplicationIcon(mEntry.notification.getPackageName());
+            mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.notification.getUser());
         } catch (PackageManager.NameNotFoundException unused) {
-            mAppName = entry.notification.getPackageName();
+            mAppName = mEntry.notification.getPackageName();
         }
     }
 
@@ -93,12 +118,16 @@
         return mKey;
     }
 
+    public NotificationEntry getEntry() {
+        return mEntry;
+    }
+
     public String getGroupId() {
         return mGroupId;
     }
 
     public String getPackageName() {
-        return entry.notification.getPackageName();
+        return mEntry.notification.getPackageName();
     }
 
     public String getAppName() {
@@ -109,9 +138,23 @@
         return mInflated;
     }
 
-    public void updateDotVisibility() {
-        if (iconView != null) {
-            iconView.updateDotVisibility(true /* animate */);
+    void updateDotVisibility() {
+        if (mIconView != null) {
+            mIconView.updateDotVisibility(true /* animate */);
+        }
+    }
+
+    BubbleView getIconView() {
+        return mIconView;
+    }
+
+    BubbleExpandedView getExpandedView() {
+        return mExpandedView;
+    }
+
+    void cleanupExpandedState() {
+        if (mExpandedView != null) {
+            mExpandedView.cleanUpExpandedState();
         }
     }
 
@@ -119,14 +162,14 @@
         if (mInflated) {
             return;
         }
-        iconView = (BubbleView) inflater.inflate(
+        mIconView = (BubbleView) inflater.inflate(
                 R.layout.bubble_view, stackView, false /* attachToRoot */);
-        iconView.setNotif(entry);
+        mIconView.setBubble(this);
+        mIconView.setAppIcon(mUserBadgedAppIcon);
 
-        expandedView = (BubbleExpandedView) inflater.inflate(
+        mExpandedView = (BubbleExpandedView) inflater.inflate(
                 R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
-        expandedView.setEntry(entry, stackView, mAppName);
-        expandedView.setOnBlockedListener(mListener);
+        mExpandedView.setBubble(this, stackView, mAppName);
 
         mInflated = true;
     }
@@ -140,46 +183,38 @@
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
     void setContentVisibility(boolean visibility) {
-        if (expandedView != null) {
-            expandedView.setContentVisibility(visibility);
+        if (mExpandedView != null) {
+            mExpandedView.setContentVisibility(visibility);
         }
     }
 
-    void setDismissed() {
-        entry.setBubbleDismissed(true);
-        // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
-        if (expandedView != null) {
-            expandedView.cleanUpExpandedState();
-        }
-    }
-
-    void setEntry(NotificationEntry entry) {
-        this.entry = entry;
+    void updateEntry(NotificationEntry entry) {
+        mEntry = entry;
         mLastUpdated = entry.notification.getPostTime();
         if (mInflated) {
-            iconView.update(entry);
-            expandedView.update(entry);
+            mIconView.update(this);
+            mExpandedView.update(this);
         }
     }
 
     /**
      * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
      */
-    public long getLastActivity() {
+    long getLastActivity() {
         return Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
      * @return the timestamp in milliseconds of the most recent notification entry for this bubble
      */
-    public long getLastUpdateTime() {
+    long getLastUpdateTime() {
         return mLastUpdated;
     }
 
     /**
      * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
      */
-    public long getLastAccessTime() {
+    long getLastAccessTime() {
         return mLastAccessed;
     }
 
@@ -187,7 +222,7 @@
      * @return the display id of the virtual display on which bubble contents is drawn.
      */
     int getDisplayId() {
-        return expandedView != null ? expandedView.getVirtualDisplayId() : INVALID_DISPLAY;
+        return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
     }
 
     /**
@@ -195,14 +230,204 @@
      */
     void markAsAccessedAt(long lastAccessedMillis) {
         mLastAccessed = lastAccessedMillis;
-        entry.setShowInShadeWhenBubble(false);
+        setShowInShadeWhenBubble(false);
+        setShowBubbleDot(false);
     }
 
     /**
-     * @return whether bubble is from a notification associated with a foreground service.
+     * Whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
      */
-    public boolean isOngoing() {
-        return entry.isForegroundService();
+    boolean showInShadeWhenBubble() {
+        return !mEntry.isRowDismissed() && !shouldSuppressNotification()
+                && (!mEntry.isClearable() || mShowInShadeWhenBubble);
+    }
+
+    /**
+     * Sets whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
+     */
+    void setShowInShadeWhenBubble(boolean showInShade) {
+        mShowInShadeWhenBubble = showInShade;
+    }
+
+    /**
+     * Sets whether the bubble for this notification should show a dot indicating updated content.
+     */
+    void setShowBubbleDot(boolean showDot) {
+        mShowBubbleUpdateDot = showDot;
+    }
+
+    /**
+     * Whether the bubble for this notification should show a dot indicating updated content.
+     */
+    boolean showBubbleDot() {
+        return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
+    }
+
+    /**
+     * Whether the flyout for the bubble should be shown.
+     */
+    boolean showFlyoutForBubble() {
+        return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
+                && !mEntry.shouldSuppressNotificationList();
+    }
+
+    /**
+     * Set whether the flyout text for the bubble should be shown when an update is received.
+     *
+     * @param suppressFlyout whether the flyout text is shown
+     */
+    void setSuppressFlyout(boolean suppressFlyout) {
+        mSuppressFlyout = suppressFlyout;
+    }
+
+    /**
+     * Returns whether the notification for this bubble is a foreground service. It shows that this
+     * is an ongoing bubble.
+     */
+    boolean isOngoing() {
+        int flags = mEntry.notification.getNotification().flags;
+        return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
+    }
+
+    float getDesiredHeight(Context context) {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return getDimenForPackageUser(context, data.getDesiredHeightResId(),
+                    mEntry.notification.getPackageName(),
+                    mEntry.notification.getUser().getIdentifier());
+        } else {
+            return data.getDesiredHeight()
+                    * context.getResources().getDisplayMetrics().density;
+        }
+    }
+
+    String getDesiredHeightString() {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return String.valueOf(data.getDesiredHeightResId());
+        } else {
+            return String.valueOf(data.getDesiredHeight());
+        }
+    }
+
+    @Nullable
+    PendingIntent getBubbleIntent(Context context) {
+        Notification notif = mEntry.notification.getNotification();
+        Notification.BubbleMetadata data = notif.getBubbleMetadata();
+        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
+            return data.getIntent();
+        }
+        return null;
+    }
+
+    Intent getSettingsIntent() {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+        intent.putExtra(Settings.EXTRA_APP_UID, mEntry.notification.getUid());
+        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        return intent;
+    }
+
+    /**
+     * Returns our best guess for the most relevant text summary of the latest update to this
+     * notification, based on its type. Returns null if there should not be an update message.
+     */
+    CharSequence getUpdateMessage(Context context) {
+        final Notification underlyingNotif = mEntry.notification.getNotification();
+        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+        try {
+            if (Notification.BigTextStyle.class.equals(style)) {
+                // Return the big text, it is big so probably important. If it's not there use the
+                // normal text.
+                CharSequence bigText =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+                return !TextUtils.isEmpty(bigText)
+                        ? bigText
+                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            } else if (Notification.MessagingStyle.class.equals(style)) {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) underlyingNotif.extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+
+                if (latestMessage != null) {
+                    final CharSequence personName = latestMessage.getSenderPerson() != null
+                            ? latestMessage.getSenderPerson().getName()
+                            : null;
+
+                    // Prepend the sender name if available since group chats also use messaging
+                    // style.
+                    if (!TextUtils.isEmpty(personName)) {
+                        return context.getResources().getString(
+                                R.string.notification_summary_message_format,
+                                personName,
+                                latestMessage.getText());
+                    } else {
+                        return latestMessage.getText();
+                    }
+                }
+            } else if (Notification.InboxStyle.class.equals(style)) {
+                CharSequence[] lines =
+                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+                // Return the last line since it should be the most recent.
+                if (lines != null && lines.length > 0) {
+                    return lines[lines.length - 1];
+                }
+            } else if (Notification.MediaStyle.class.equals(style)) {
+                // Return nothing, media updates aren't typically useful as a text update.
+                return null;
+            } else {
+                // Default to text extra.
+                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            }
+        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // No use crashing, we'll just return null and the caller will assume there's no update
+            // message.
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
+        PackageManager pm = context.getPackageManager();
+        Resources r;
+        if (pkg != null) {
+            try {
+                if (userId == UserHandle.USER_ALL) {
+                    userId = UserHandle.USER_SYSTEM;
+                }
+                r = pm.getResourcesForApplicationAsUser(pkg, userId);
+                return r.getDimensionPixelSize(resId);
+            } catch (PackageManager.NameNotFoundException ex) {
+                // Uninstalled, don't care
+            } catch (Resources.NotFoundException e) {
+                // Invalid res id, return 0 and user our default
+                Log.e(TAG, "Couldn't find desired height res id", e);
+            }
+        }
+        return 0;
+    }
+
+    private boolean shouldSuppressNotification() {
+        return mEntry.getBubbleMetadata() != null
+                && mEntry.getBubbleMetadata().isNotificationSuppressed();
+    }
+
+    boolean shouldAutoExpand() {
+        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
+        return metadata != null && metadata.getAutoExpandBubble();
     }
 
     @Override
@@ -210,6 +435,20 @@
         return "Bubble{" + mKey + '}';
     }
 
+    /**
+     * Description of current bubble state.
+     */
+    public void dump(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("key: "); pw.println(mKey);
+        pw.print("  showInShade:   "); pw.println(showInShadeWhenBubble());
+        pw.print("  showDot:       "); pw.println(showBubbleDot());
+        pw.print("  showFlyout:    "); pw.println(showFlyoutForBubble());
+        pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
+        pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
+        pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a23c99e..72ada6e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,21 +16,24 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 
@@ -39,27 +42,23 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.app.ActivityManager;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseSetArray;
 import android.view.Display;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
@@ -73,20 +72,26 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -101,12 +106,12 @@
 @Singleton
 public class BubbleController implements ConfigurationController.ConfigurationListener {
 
-    private static final String TAG = "BubbleController";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
     @Retention(SOURCE)
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
-            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
+            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
+            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -117,35 +122,28 @@
     static final int DISMISS_NOTIF_CANCEL = 5;
     static final int DISMISS_ACCESSIBILITY_ACTION = 6;
     static final int DISMISS_NO_LONGER_BUBBLE = 7;
+    static final int DISMISS_USER_CHANGED = 8;
+    static final int DISMISS_GROUP_CANCELLED = 9;
+    static final int DISMISS_INVALID_INTENT = 10;
 
     public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
 
-    // Enables some subset of notifs to automatically become bubbles
-    public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-
-    /** Flag to enable or disable the entire feature */
-    private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
-    /** Auto bubble flags set whether different notif types should be presented as a bubble */
-    private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
-    private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
-    private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
-
-    /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
-    private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
-
-    private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
-    private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
-
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
     private final BubbleTaskStackListener mTaskStackListener;
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+    private final NotificationGroupManager mNotificationGroupManager;
 
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
 
+    // Tracks the id of the current (foreground) user.
+    private int mCurrentUserId;
+    // Saves notification keys of active bubbles when users are switched.
+    private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
     private final ZenModeController mZenModeController;
@@ -157,6 +155,9 @@
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
 
+    // Listens to user switch so bubbles can be saved and restored.
+    private final NotificationLockscreenUserManager mNotifUserManager;
+
     /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -211,28 +212,38 @@
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         this(context, statusBarWindowController, data, null /* synchronizer */,
-                configurationController, interruptionStateProvider, zenModeController);
+                configurationController, interruptionStateProvider, zenModeController,
+                notifUserManager, groupManager);
     }
 
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         mContext = context;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
+        mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
         mZenModeController.addCallback(new ZenModeController.Callback() {
             @Override
             public void onZenChanged(int zen) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
 
             @Override
             public void onConfigChanged(ZenModeConfig config) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
         });
 
@@ -244,6 +255,24 @@
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
         mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
+        mNotificationGroupManager = groupManager;
+        mNotificationGroupManager.addOnGroupChangeListener(
+                new NotificationGroupManager.OnGroupChangeListener() {
+                    @Override
+                    public void onGroupSuppressionChanged(
+                            NotificationGroupManager.NotificationGroup group,
+                            boolean suppressed) {
+                        // More notifications could be added causing summary to no longer
+                        // be suppressed -- in this case need to remove the key.
+                        final String groupKey = group.summary != null
+                                ? group.summary.notification.getGroupKey()
+                                : null;
+                        if (!suppressed && groupKey != null
+                                && mBubbleData.isSummarySuppressed(groupKey)) {
+                            mBubbleData.removeSuppressedSummary(groupKey);
+                        }
+                    }
+                });
 
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
@@ -261,6 +290,16 @@
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+        mSavedBubbleKeysPerUser = new SparseSetArray<>();
+        mCurrentUserId = mNotifUserManager.getCurrentUserId();
+        mNotifUserManager.addUserChangedListener(
+                newUserId -> {
+                    saveBubbles(mCurrentUserId);
+                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+                    restoreBubbles(newUserId);
+                    mCurrentUserId = newUserId;
+                });
     }
 
     /**
@@ -271,19 +310,55 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
             ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
-            // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
-            //  scrim between bubble and the shade
-            int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
-            sbv.addView(mStackView, bubblePosition,
+            int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+            int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
+            sbv.addView(mStackView, stackIndex,
                     new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
-
-            updateStackViewForZenConfig();
         }
     }
 
+    /**
+     * Records the notification key for any active bubbles. These are used to restore active
+     * bubbles when the user returns to the foreground.
+     *
+     * @param userId the id of the user
+     */
+    private void saveBubbles(@UserIdInt int userId) {
+        // First clear any existing keys that might be stored.
+        mSavedBubbleKeysPerUser.remove(userId);
+        // Add in all active bubbles for the current user.
+        for (Bubble bubble: mBubbleData.getBubbles()) {
+            mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+        }
+    }
+
+    /**
+     * Promotes existing notifications to Bubbles if they were previously bubbles.
+     *
+     * @param userId the id of the user
+     */
+    private void restoreBubbles(@UserIdInt int userId) {
+        NotificationData notificationData =
+                mNotificationEntryManager.getNotificationData();
+        ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
+        if (savedBubbleKeys == null) {
+            // There were no bubbles saved for this used.
+            return;
+        }
+        for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) {
+            if (savedBubbleKeys.contains(e.key)
+                    && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
+                    && canLaunchInActivityView(mContext, e)) {
+                updateBubble(e, /* suppressFlyout= */ true);
+            }
+        }
+        // Finally, remove the entries for this user now that bubbles are restored.
+        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
+    }
+
     @Override
     public void onUiModeChanged() {
         if (mStackView != null) {
@@ -301,8 +376,8 @@
     @Override
     public void onConfigChanged(Configuration newConfig) {
         if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
-            mStackView.onOrientationChanged();
             mOrientation = newConfig.orientation;
+            mStackView.onOrientationChanged(newConfig.orientation);
         }
     }
 
@@ -360,6 +435,25 @@
         mBubbleData.setExpanded(false /* expanded */);
     }
 
+    /**
+     * True if either:
+     * (1) There is a bubble associated with the provided key and if its notification is hidden
+     *     from the shade.
+     * (2) There is a group summary associated with the provided key that is hidden from the shade
+     *     because it has been dismissed but still has child bubbles active.
+     *
+     * False otherwise.
+     */
+    public boolean isBubbleNotificationSuppressedFromShade(String key) {
+        boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
+                && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
+        NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+        String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
+        boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
+        return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
+    }
+
     void selectBubble(Bubble bubble) {
         mBubbleData.setSelectedBubble(bubble);
     }
@@ -406,11 +500,15 @@
      * @param notif the notification associated with this bubble.
      */
     void updateBubble(NotificationEntry notif) {
+        updateBubble(notif, /* supressFlyout */ false);
+    }
+
+    void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
         // If this is an interruptive notif, mark that it's interrupted
-        if (notif.importance >= NotificationManager.IMPORTANCE_HIGH) {
+        if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        mBubbleData.notificationEntryUpdated(notif);
+        mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
     }
 
     /**
@@ -423,7 +521,7 @@
         // TEMP: refactor to change this to pass entry
         Bubble bubble = mBubbleData.getBubbleWithKey(key);
         if (bubble != null) {
-            mBubbleData.notificationEntryRemoved(bubble.entry, reason);
+            mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
         }
     }
 
@@ -432,33 +530,50 @@
             new NotificationRemoveInterceptor() {
             @Override
             public boolean onNotificationRemoveRequested(String key, int reason) {
-                if (!mBubbleData.hasBubbleWithKey(key)) {
+                NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+                String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+                ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+                boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
+                boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
+                        && mBubbleData.getSummaryKey(groupKey).equals(key));
+                boolean isSummary = entry != null
+                        && entry.notification.getNotification().isGroupSummary();
+                boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
+                        && bubbleChildren != null && !bubbleChildren.isEmpty();
+
+                if (!inBubbleData && !isSummaryOfBubbles) {
                     return false;
                 }
-                NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
 
                 final boolean isClearAll = reason == REASON_CANCEL_ALL;
-                final boolean isUserDimiss = reason == REASON_CANCEL;
+                final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
                 final boolean isAppCancel = reason == REASON_APP_CANCEL
                         || reason == REASON_APP_CANCEL_ALL;
+                final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
 
                 // Need to check for !appCancel here because the notification may have
                 // previously been dismissed & entry.isRowDismissed would still be true
-                boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
-                        || isClearAll || isUserDimiss;
+                boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
+                        || isClearAll || isUserDimiss || isSummaryCancel;
+
+                if (isSummaryOfBubbles) {
+                    return handleSummaryRemovalInterception(entry, userRemovedNotif);
+                }
 
                 // The bubble notification sticks around in the data as long as the bubble is
                 // not dismissed and the app hasn't cancelled the notification.
-                boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
-                        && userRemovedNotif;
+                Bubble bubble = mBubbleData.getBubbleWithKey(key);
+                boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
                 if (bubbleExtended) {
-                    entry.setShowInShadeWhenBubble(false);
+                    bubble.setShowInShadeWhenBubble(false);
+                    bubble.setShowBubbleDot(false);
                     if (mStackView != null) {
                         mStackView.updateDotVisibility(entry.key);
                     }
                     mNotificationEntryManager.updateNotifications();
                     return true;
-                } else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
+                } else if (!userRemovedNotif && entry != null) {
                     // This wasn't a user removal so we should remove the bubble as well
                     mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                     return false;
@@ -467,24 +582,56 @@
             }
         };
 
+    private boolean handleSummaryRemovalInterception(NotificationEntry summary,
+            boolean userRemovedNotif) {
+        String groupKey = summary.notification.getGroupKey();
+        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+        if (userRemovedNotif) {
+            // If it's a user dismiss we mark the children to be hidden from the shade.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                // As far as group manager is concerned, once a child is no longer shown
+                // in the shade, it is essentially removed.
+                mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
+                bubbleChild.setShowInShadeWhenBubble(false);
+                bubbleChild.setShowBubbleDot(false);
+                if (mStackView != null) {
+                    mStackView.updateDotVisibility(bubbleChild.getKey());
+                }
+            }
+            // And since all children are removed, remove the summary.
+            mNotificationGroupManager.onEntryRemoved(summary);
+
+            // If the summary was auto-generated we don't need to keep that notification around
+            // because apps can't cancel it; so we only intercept & suppress real summaries.
+            boolean isAutogroupSummary = (summary.notification.getNotification().flags
+                    & FLAG_AUTOGROUP_SUMMARY) != 0;
+            if (!isAutogroupSummary) {
+                mBubbleData.addSummaryToSuppress(summary.notification.getGroupKey(),
+                        summary.key);
+                // Tell shade to update for the suppression
+                mNotificationEntryManager.updateNotifications();
+            }
+            return !isAutogroupSummary;
+        } else {
+            // If it's not a user dismiss it's a cancel.
+            mBubbleData.removeSuppressedSummary(groupKey);
+
+            // Remove any associated bubble children.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
+                        DISMISS_GROUP_CANCELLED);
+            }
+            return false;
+        }
+    }
+
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
-            if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
-                    && canLaunchInActivityView(mContext, entry)) {
-                updateShowInShadeForSuppressNotification(entry);
-            }
-        }
-
-        @Override
-        public void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
                 updateBubble(entry);
@@ -493,17 +640,13 @@
 
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry);
             if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) {
                 // It was previously a bubble but no longer a bubble -- lets remove it
                 removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
             } else if (shouldBubble) {
-                updateShowInShadeForSuppressNotification(entry);
-                entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+                Bubble b = mBubbleData.getBubbleWithKey(entry.key);
                 updateBubble(entry);
             }
         }
@@ -540,27 +683,60 @@
             }
 
             // Do removals, if any.
-            for (Pair<Bubble, Integer> removed : update.removedBubbles) {
+            ArrayList<Pair<Bubble, Integer>> removedBubbles =
+                    new ArrayList<>(update.removedBubbles);
+            for (Pair<Bubble, Integer> removed : removedBubbles) {
                 final Bubble bubble = removed.first;
                 @DismissReason final int reason = removed.second;
                 mStackView.removeBubble(bubble);
 
-                if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
-                        && !bubble.entry.showInShadeWhenBubble()) {
-                    // The bubble is gone & the notification is gone, time to actually remove it
-                    mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
-                            UNDEFINED_DISMISS_REASON);
-                } else {
-                    // Update the flag for SysUI
-                    bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
+                // If the bubble is removed for user switching, leave the notification in place.
+                if (reason != DISMISS_USER_CHANGED) {
+                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
+                            && !bubble.showInShadeWhenBubble()) {
+                        // The bubble is gone & the notification is gone, time to actually remove it
+                        mNotificationEntryManager.performRemoveNotification(
+                                bubble.getEntry().notification, UNDEFINED_DISMISS_REASON);
+                    } else {
+                        // Update the flag for SysUI
+                        bubble.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;
 
-                    // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
-                    // get right result back
-                    try {
-                        mBarService.onNotificationBubbleChanged(bubble.getKey(),
-                                false /* isBubble */);
-                    } catch (RemoteException e) {
-                        // Bad things have happened
+                        // Make sure NoMan knows it's not a bubble anymore so anyone querying it
+                        // will get right result back
+                        try {
+                            mBarService.onNotificationBubbleChanged(bubble.getKey(),
+                                    false /* isBubble */);
+                        } catch (RemoteException e) {
+                            // Bad things have happened
+                        }
+                    }
+
+                    // Check if removed bubble has an associated suppressed group summary that needs
+                    // to be removed now.
+                    final String groupKey = bubble.getEntry().notification.getGroupKey();
+                    if (mBubbleData.isSummarySuppressed(groupKey)
+                            && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+                        // Time to actually remove the summary.
+                        String notifKey = mBubbleData.getSummaryKey(groupKey);
+                        mBubbleData.removeSuppressedSummary(groupKey);
+                        NotificationEntry entry =
+                                mNotificationEntryManager.getNotificationData().get(notifKey);
+                        mNotificationEntryManager.performRemoveNotification(
+                                entry.notification, UNDEFINED_DISMISS_REASON);
+                    }
+
+                    // Check if summary should be removed from NoManGroup
+                    NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
+                            bubble.getEntry().notification);
+                    if (summary != null) {
+                        ArrayList<NotificationEntry> summaryChildren =
+                                mNotificationGroupManager.getLogicalChildren(summary.notification);
+                        boolean isSummaryThisNotif = summary.key.equals(bubble.getEntry().key);
+                        if (!isSummaryThisNotif
+                                && (summaryChildren == null || summaryChildren.isEmpty())) {
+                            mNotificationEntryManager.performRemoveNotification(
+                                    summary.notification, UNDEFINED_DISMISS_REASON);
+                        }
                     }
                 }
             }
@@ -575,6 +751,10 @@
 
             if (update.selectionChanged) {
                 mStackView.setSelectedBubble(update.selectedBubble);
+                if (update.selectedBubble != null) {
+                    mNotificationGroupManager.updateSuppression(
+                            update.selectedBubble.getEntry());
+                }
             }
 
             // Expanding? Apply this last.
@@ -585,7 +765,7 @@
             mNotificationEntryManager.updateNotifications();
             updateStack();
 
-            if (DEBUG) {
+            if (DEBUG_BUBBLE_CONTROLLER) {
                 Log.d(TAG, "[BubbleData]");
                 Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
                         mBubbleData.getSelectedBubble()));
@@ -600,34 +780,6 @@
     };
 
     /**
-     * Updates the stack view's suppression flags from the latest config from the zen (do not
-     * disturb) controller.
-     */
-    private void updateStackViewForZenConfig() {
-        final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
-
-        if (zenModeConfig == null || mStackView == null) {
-            return;
-        }
-
-        final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
-        final boolean hideNotificationDotsSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
-        final boolean dontPopNotifsOnScreenSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
-        final boolean hideFromPullDownShadeSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
-
-        final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
-
-        mStackView.setSuppressNewDot(
-                dndEnabled && hideNotificationDotsSelected);
-        mStackView.setSuppressFlyout(
-                dndEnabled && (dontPopNotifsOnScreenSelected
-                        || hideFromPullDownShadeSelected));
-    }
-
-    /**
      * Lets any listeners know if bubble state has changed.
      * Updates the visibility of the bubbles based on current state.
      * Does not un-bubble, just hides or un-hides. Notifies any
@@ -697,46 +849,16 @@
     }
 
     /**
-     * Whether the notification should automatically bubble or not. Gated by secure settings flags.
+     * Description of current bubble state.
      */
-    @VisibleForTesting
-    protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
-        if (entry.isBubbleDismissed()) {
-            return false;
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BubbleController state:");
+        mBubbleData.dump(fd, pw, args);
+        pw.println();
+        if (mStackView != null) {
+            mStackView.dump(fd, pw, args);
         }
-        StatusBarNotification n = entry.notification;
-
-        boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-
-        boolean hasRemoteInput = false;
-        if (n.getNotification().actions != null) {
-            for (Notification.Action action : n.getNotification().actions) {
-                if (action.getRemoteInputs() != null) {
-                    hasRemoteInput = true;
-                    break;
-                }
-            }
-        }
-        boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
-                && n.isOngoing();
-        boolean isMusic = n.getNotification().hasMediaSession();
-        boolean isImportantOngoing = isMusic || isCall;
-
-        Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
-        boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
-                || (isImportantOngoing && autoBubbleOngoing)
-                || autoBubbleAll;
-    }
-
-    private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
-        boolean suppressNotification = entry.getBubbleMetadata() != null
-                && entry.getBubbleMetadata().isNotificationSuppressed()
-                && isForegroundApp(mContext, entry.notification.getPackageName());
-        entry.setShowInShadeWhenBubble(!suppressNotification);
+        pw.println();
     }
 
     static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
@@ -757,18 +879,6 @@
     }
 
     /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param context application context.
-     * @param pkgName application package name.
-     */
-    public static boolean isForegroundApp(Context context, String pkgName) {
-        ActivityManager am = context.getSystemService(ActivityManager.class);
-        List<RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    /**
      * This task stack listener is responsible for responding to tasks moved to the front
      * which are on the default (main) display. When this happens, expanded bubbles must be
      * collapsed so the user may interact with the app which was just moved to the front.
@@ -782,7 +892,9 @@
         @Override
         public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
             if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
-                mBubbleData.setExpanded(false);
+                if (!mStackView.isExpansionAnimating()) {
+                    mBubbleData.setExpanded(false);
+                }
             }
         }
 
@@ -802,50 +914,25 @@
 
         @Override
         public void onSingleTaskDisplayDrawn(int displayId) {
-            final Bubble expandedBubble = getExpandedBubble(mContext);
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
             if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
                 expandedBubble.setContentVisibility(true);
             }
         }
-    }
 
-    private static boolean shouldAutoBubbleMessages(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleOngoing(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleAll(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
-    }
-
-    static boolean shouldUseContentIntent(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
-    }
-
-    private static boolean areBubblesEnabled(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLES, 1) != 0;
-    }
-
-    /** Default stiffness to use for bubble physics animations. */
-    public static int getBubbleStiffness(Context context, int defaultStiffness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
-    }
-
-    /** Default bounciness/damping ratio to use for bubble physics animations. */
-    public static float getBubbleBounciness(Context context, float defaultBounciness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(),
-                BUBBLE_BOUNCINESS,
-                (int) (defaultBounciness * 100)) / 100f;
+        @Override
+        public void onSingleTaskDisplayEmpty(int displayId) {
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
+            int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
+            if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
+                mBubbleData.setExpanded(false);
+            }
+            mBubbleData.notifyDisplayEmpty(displayId);
+        }
     }
 
     /**
@@ -891,32 +978,12 @@
     }
 
     /** PinnedStackListener that dispatches IME visibility updates to the stack. */
-    private class BubblesImeListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) throws RemoteException {}
-
+    private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mStackView != null && mStackView.getBubbleCount() > 0) {
                 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
             }
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-                throws RemoteException {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 5575b35..d43e030 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -16,6 +16,9 @@
 package com.android.systemui.bubbles;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import static java.util.stream.Collectors.toList;
 
@@ -33,11 +36,12 @@
 import com.android.systemui.bubbles.BubbleController.DismissReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,8 +55,7 @@
 @Singleton
 public class BubbleData {
 
-    private static final String TAG = "BubbleData";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
 
     private static final int MAX_BUBBLES = 5;
 
@@ -123,6 +126,19 @@
     @Nullable
     private Listener mListener;
 
+    /**
+     * We track groups with summaries that aren't visibly displayed but still kept around because
+     * the bubble(s) associated with the summary still exist.
+     *
+     * The summary must be kept around so that developers can cancel it (and hence the bubbles
+     * associated with it). This list is used to check if the summary should be hidden from the
+     * shade.
+     *
+     * Key: group key of the NotificationEntry
+     * Value: key of the NotificationEntry
+     */
+    private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
+
     @Inject
     public BubbleData(Context context) {
         mContext = context;
@@ -148,7 +164,7 @@
     }
 
     public void setExpanded(boolean expanded) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpanded: " + expanded);
         }
         setExpandedInternal(expanded);
@@ -156,29 +172,34 @@
     }
 
     public void setSelectedBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubble: " + bubble);
         }
         setSelectedBubbleInternal(bubble);
         dispatchPendingChanges();
     }
 
-    public void notificationEntryUpdated(NotificationEntry entry) {
-        if (DEBUG) {
+    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + entry);
         }
         Bubble bubble = getBubbleWithKey(entry.key);
+        suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
+
         if (bubble == null) {
             // Create a new bubble
-            bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
+            bubble = new Bubble(mContext, entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doAdd(bubble);
             trim();
         } else {
             // Updates an existing bubble
-            bubble.setEntry(entry);
+            bubble.updateEntry(entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doUpdate(bubble);
         }
-        if (shouldAutoExpand(entry)) {
+
+        if (bubble.shouldAutoExpand()) {
             setSelectedBubbleInternal(bubble);
             if (!mExpanded) {
                 setExpandedInternal(true);
@@ -186,11 +207,14 @@
         } else if (mSelectedBubble == null) {
             setSelectedBubbleInternal(bubble);
         }
+        boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
+        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
+        bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
         dispatchPendingChanges();
     }
 
     public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
         }
         doRemove(entry.key, reason);
@@ -222,8 +246,59 @@
         dispatchPendingChanges();
     }
 
+    /**
+     * Adds a group key indicating that the summary for this group should be suppressed.
+     *
+     * @param groupKey the group key of the group whose summary should be suppressed.
+     * @param notifKey the notification entry key of that summary.
+     */
+    void addSummaryToSuppress(String groupKey, String notifKey) {
+        mSuppressedGroupKeys.put(groupKey, notifKey);
+    }
+
+    /**
+     * Retrieves the notif entry key of the summary associated with the provided group key.
+     *
+     * @param groupKey the group to look up
+     * @return the key for the {@link NotificationEntry} that is the summary of this group.
+     */
+    String getSummaryKey(String groupKey) {
+        return mSuppressedGroupKeys.get(groupKey);
+    }
+
+    /**
+     * Removes a group key indicating that summary for this group should no longer be suppressed.
+     */
+    void removeSuppressedSummary(String groupKey) {
+        mSuppressedGroupKeys.remove(groupKey);
+    }
+
+    /**
+     * Whether the summary for the provided group key is suppressed.
+     */
+    boolean isSummarySuppressed(String groupKey) {
+        return mSuppressedGroupKeys.containsKey(groupKey);
+    }
+
+    /**
+     * Retrieves any bubbles that are part of the notification group represented by the provided
+     * group key.
+     */
+    ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+        ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+        if (groupKey == null) {
+            return bubbleChildren;
+        }
+        for (Bubble b : mBubbles) {
+            if (groupKey.equals(b.getEntry().notification.getGroupKey())) {
+                bubbleChildren.add(b);
+            }
+        }
+        return bubbleChildren;
+    }
+
     private void doAdd(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doAdd: " + bubble);
         }
         int minInsertPoint = 0;
@@ -256,7 +331,7 @@
     }
 
     private void doUpdate(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doUpdate: " + bubble);
         }
         mStateChange.updatedBubble = bubble;
@@ -301,12 +376,11 @@
             Bubble newSelected = mBubbles.get(newIndex);
             setSelectedBubbleInternal(newSelected);
         }
-        bubbleToRemove.setDismissed();
-        maybeSendDeleteIntent(reason, bubbleToRemove.entry);
+        maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
     }
 
     public void dismissAll(@DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "dismissAll: reason=" + reason);
         }
         if (mBubbles.isEmpty()) {
@@ -316,13 +390,28 @@
         setSelectedBubbleInternal(null);
         while (!mBubbles.isEmpty()) {
             Bubble bubble = mBubbles.remove(0);
-            bubble.setDismissed();
-            maybeSendDeleteIntent(reason, bubble.entry);
+            maybeSendDeleteIntent(reason, bubble.getEntry());
             mStateChange.bubbleRemoved(bubble, reason);
         }
         dispatchPendingChanges();
     }
 
+    /**
+     * Indicates that the provided display is no longer in use and should be cleaned up.
+     *
+     * @param displayId the id of the display to clean up.
+     */
+    void notifyDisplayEmpty(int displayId) {
+        for (Bubble b : mBubbles) {
+            if (b.getDisplayId() == displayId) {
+                if (b.getExpandedView() != null) {
+                    b.getExpandedView().notifyDisplayEmpty();
+                }
+                return;
+            }
+        }
+    }
+
     private void dispatchPendingChanges() {
         if (mListener != null && mStateChange.anythingChanged()) {
             mListener.applyUpdate(mStateChange);
@@ -336,7 +425,7 @@
      * @param bubble the new selected bubble
      */
     private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
         }
         if (Objects.equals(bubble, mSelectedBubble)) {
@@ -361,7 +450,7 @@
      * @param shouldExpand the new requested state
      */
     private void setExpandedInternal(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
         }
         if (mExpanded == shouldExpand) {
@@ -396,7 +485,7 @@
                     // bubble remains on top.
                     mBubbles.remove(mSelectedBubble);
                     mBubbles.add(0, mSelectedBubble);
-                    packGroup(0);
+                    mStateChange.orderChanged |= packGroup(0);
                 }
             }
         }
@@ -466,7 +555,7 @@
      * @return true if the position of any bubbles has changed as a result
      */
     private boolean packGroup(int position) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "packGroup: position=" + position);
         }
         Bubble groupStart = mBubbles.get(position);
@@ -495,7 +584,7 @@
      * @return true if the position of any bubbles changed as a result
      */
     private boolean repackAll() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "repackAll()");
         }
         if (mBubbles.isEmpty()) {
@@ -550,28 +639,6 @@
         }
     }
 
-    private void onBubbleBlocked(NotificationEntry entry) {
-        final String blockedGroupId = Bubble.groupId(entry);
-        int selectedIndex = mBubbles.indexOf(mSelectedBubble);
-        for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
-            Bubble bubble = i.next();
-            if (bubble.getGroupId().equals(blockedGroupId)) {
-                mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
-                i.remove();
-            }
-        }
-        if (mBubbles.isEmpty()) {
-            setExpandedInternal(false);
-            setSelectedBubbleInternal(null);
-        } else if (!mBubbles.contains(mSelectedBubble)) {
-            // choose a new one
-            int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
-            Bubble newSelected = mBubbles.get(newIndex);
-            setSelectedBubbleInternal(newSelected);
-        }
-        dispatchPendingChanges();
-    }
-
     private int indexForKey(String key) {
         for (int i = 0; i < mBubbles.size(); i++) {
             Bubble bubble = mBubbles.get(i);
@@ -610,9 +677,21 @@
         mListener = listener;
     }
 
-    boolean shouldAutoExpand(NotificationEntry entry) {
-        Notification.BubbleMetadata metadata = entry.getBubbleMetadata();
-        return metadata != null && metadata.getAutoExpandBubble()
-                && BubbleController.isForegroundApp(mContext, entry.notification.getPackageName());
+    /**
+     * Description of current bubble data state.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("selected: "); pw.println(mSelectedBubble != null
+                ? mSelectedBubble.getKey()
+                : "null");
+        pw.print("expanded: "); pw.println(mExpanded);
+        pw.print("count:    "); pw.println(mBubbles.size());
+        for (Bubble bubble : mBubbles) {
+            bubble.dump(fd, pw, args);
+        }
+        pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size());
+        for (String key : mSuppressedGroupKeys.keySet()) {
+            pw.println("   suppressing: " + key);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
new file mode 100644
index 0000000..b702d06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bubbles;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
+ * package.
+ */
+public class BubbleDebugConfig {
+
+    // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
+    // log output. This makes it easy to identify the origin of the log message when sifting
+    // through a large amount of log output from multiple sources. However, it also makes trying
+    // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
+    // setting this constant to true, log messages from the Bubbles package will be tagged with
+    // their class names instead fot the generic tag.
+    static final boolean TAG_WITH_CLASS_NAME = false;
+
+    // Default log tag for the Bubbles package.
+    static final String TAG_BUBBLES = "Bubbles";
+
+    static final boolean DEBUG_BUBBLE_CONTROLLER = false;
+    static final boolean DEBUG_BUBBLE_DATA = false;
+    static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
+    static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
index 4db1e27..9db371e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
@@ -16,18 +16,13 @@
 
 package com.android.systemui.bubbles;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -37,14 +32,13 @@
 
 /** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
 public class BubbleDismissView extends FrameLayout {
-    /** Duration for animations involving the dismiss target text/icon/gradient. */
+    /** Duration for animations involving the dismiss target text/icon. */
     private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
-
-    private View mDismissGradient;
+    private static final float SCALE_FOR_POP = 1.2f;
+    private static final float SCALE_FOR_DISMISS = 0.9f;
 
     private LinearLayout mDismissTarget;
     private ImageView mDismissIcon;
-    private TextView mDismissText;
     private View mDismissCircle;
 
     private SpringAnimation mDismissTargetAlphaSpring;
@@ -54,36 +48,15 @@
         super(context);
         setVisibility(GONE);
 
-        mDismissGradient = new FrameLayout(mContext);
-
-        FrameLayout.LayoutParams gradientParams =
-                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
-        gradientParams.gravity = Gravity.BOTTOM;
-        mDismissGradient.setLayoutParams(gradientParams);
-
-        Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
-        gradient.setAlpha((int) (255 * 0.85f));
-        mDismissGradient.setBackground(gradient);
-
-        mDismissGradient.setVisibility(GONE);
-        addView(mDismissGradient);
-
         LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
         mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
         mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
-        mDismissText = findViewById(R.id.bubble_dismiss_text);
         mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
 
         // Set up the basic target area animations. These are very simple animations that don't need
         // fancy interpolators.
         final AccelerateDecelerateInterpolator interpolator =
                 new AccelerateDecelerateInterpolator();
-        mDismissGradient.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
-        mDismissText.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
         mDismissIcon.animate()
                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
                 .setInterpolator(interpolator);
@@ -108,110 +81,58 @@
             // safely assume it was animating out rather than in.
             if (alpha < 0.5f) {
                 // If the alpha spring was animating the view out, set it to GONE when it's done.
-                setVisibility(GONE);
+                setVisibility(INVISIBLE);
             }
         });
     }
 
-    /** Springs in the dismiss target and fades in the gradient. */
+    /** Springs in the dismiss target. */
     void springIn() {
         setVisibility(View.VISIBLE);
 
-        // Fade in the dismiss target (icon + text).
+        // Fade in the dismiss target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(1f)
+                .scaleY(1f)
+                .alpha(1f);
         mDismissTarget.setAlpha(0f);
         mDismissTargetAlphaSpring.animateToFinalPosition(1f);
 
-        // Spring up the dismiss target (icon + text).
+        // Spring up the dismiss target.
         mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
         mDismissTargetVerticalSpring.animateToFinalPosition(0);
 
-        // Fade in the gradient.
-        mDismissGradient.setVisibility(VISIBLE);
-        mDismissGradient.animate().alpha(1f);
+        mDismissCircle.setAlpha(0f);
+        mDismissCircle.setScaleX(SCALE_FOR_POP);
+        mDismissCircle.setScaleY(SCALE_FOR_POP);
 
-        // Make sure the dismiss elements are in the separated position (in case we hid the target
-        // while they were condensed to cover the bubbles being in the target).
-        mDismissIcon.setAlpha(1f);
-        mDismissIcon.setScaleX(1f);
-        mDismissIcon.setScaleY(1f);
-        mDismissIcon.setTranslationX(0f);
-        mDismissText.setAlpha(1f);
-        mDismissText.setTranslationX(0f);
+        // Fade in circle and reduce size.
+        mDismissCircle.animate()
+                .alpha(1f)
+                .scaleX(1f)
+                .scaleY(1f);
     }
 
-    /** Springs out the dismiss target and fades out the gradient. */
+    /** Springs out the dismiss target. */
     void springOut() {
+        // Fade out the target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
+                .alpha(0f);
+
         // Fade out the target.
         mDismissTargetAlphaSpring.animateToFinalPosition(0f);
 
         // Spring the target down a bit.
         mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
 
-        // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
-        mDismissGradient.animate().alpha(0f).withEndAction(
-                () -> mDismissGradient.setVisibility(GONE));
-
-        // Pop out the dismiss circle.
-        mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
-    }
-
-    /**
-     * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
-     * text.
-     */
-    void animateEncircleCenterWithX(boolean encircle) {
-        // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
-        // the X icon over the bubbles), or back to normal if we're un-encircling.
-        final float textTranslation = encircle
-                ? -mDismissIcon.getWidth() / 4f
-                : 0f;
-
-        // Center the icon if we're encircling, or put it back to normal if not.
-        final float iconTranslation = encircle
-                ? mDismissTarget.getWidth() / 2f
-                - mDismissIcon.getWidth() / 2f
-                - mDismissIcon.getLeft()
-                : 0f;
-
-        // Fade in/out the text and translate it.
-        mDismissText.animate()
-                .alpha(encircle ? 0f : 1f)
-                .translationX(textTranslation);
-
-        mDismissIcon.animate()
-                .setDuration(150)
-                .translationX(iconTranslation);
-
-        // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
-        // themselves).
-        mDismissGradient.animate()
-                .alpha(encircle ? 0f : 1f);
-
-        // Prepare the circle to be 'dropped in'.
-        if (encircle) {
-            mDismissCircle.setAlpha(0f);
-            mDismissCircle.setScaleX(1.2f);
-            mDismissCircle.setScaleY(1.2f);
-        }
-
-        // Drop in the circle, or pull it back up.
+        // Pop out the circle.
         mDismissCircle.animate()
-                .alpha(encircle ? 1f : 0f)
-                .scaleX(encircle ? 1f : 0f)
-                .scaleY(encircle ? 1f : 0f);
-    }
-
-    /** Animates the circle and the centered icon out. */
-    void animateEncirclingCircleDisappearance() {
-        // Pop out the dismiss icon and circle.
-        mDismissIcon.animate()
-                .setDuration(50)
-                .scaleX(0.9f)
-                .scaleY(0.9f)
-                .alpha(0f);
-        mDismissCircle.animate()
-                .scaleX(0.9f)
-                .scaleY(0.9f)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
                 .alpha(0f);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index de08a8c..521ebde 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -18,14 +18,15 @@
 
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.app.ActivityView;
-import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -35,18 +36,17 @@
 import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.StatsLog;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -54,15 +54,23 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
  */
 public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
-    private static final String TAG = "BubbleExpandedView";
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+
+    private enum ActivityViewStatus {
+        // ActivityView is being initialized, cannot start an activity yet.
+        INITIALIZING,
+        // ActivityView is initialized, and ready to start an activity.
+        INITIALIZED,
+        // Activity runs in the ActivityView.
+        ACTIVITY_STARTED,
+        // ActivityView is released, so activity launching will no longer be permitted.
+        RELEASED,
+    }
 
     // The triangle pointing to the expanded view
     private View mPointerView;
@@ -71,50 +79,90 @@
     private AlphaOptimizedButton mSettingsIcon;
 
     // Views for expanded state
-    private ExpandableNotificationRow mNotifRow;
     private ActivityView mActivityView;
 
-    private boolean mActivityViewReady = false;
+    private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
+    private int mTaskId = -1;
+
     private PendingIntent mBubbleIntent;
 
     private boolean mKeyboardVisible;
     private boolean mNeedsNewHeight;
 
+    private Point mDisplaySize;
     private int mMinHeight;
     private int mSettingsIconHeight;
-    private int mBubbleHeight;
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
+    private Rect mTempRect = new Rect();
+    private int[] mTempLoc = new int[2];
+    private int mExpandedViewTouchSlop;
 
-    private NotificationEntry mEntry;
+    private Bubble mBubble;
     private PackageManager mPm;
     private String mAppName;
     private Drawable mAppIcon;
 
-    private INotificationManager mNotificationManagerService;
     private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private WindowManager mWindowManager;
 
     private BubbleStackView mStackView;
 
-    private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
-
     private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
         public void onActivityViewReady(ActivityView view) {
-            if (!mActivityViewReady) {
-                mActivityViewReady = true;
-                // Custom options so there is no activity transition animation
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                        0 /* enterResId */, 0 /* exitResId */);
-                // Post to keep the lifecycle normal
-                post(() -> mActivityView.startActivity(mBubbleIntent, options));
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            switch (mActivityViewStatus) {
+                case INITIALIZING:
+                case INITIALIZED:
+                    // Custom options so there is no activity transition animation
+                    ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                            0 /* enterResId */, 0 /* exitResId */);
+                    // Post to keep the lifecycle normal
+                    post(() -> {
+                        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                            Log.d(TAG, "onActivityViewReady: calling startActivity, "
+                                    + "bubble=" + getBubbleKey());
+                        }
+                        try {
+                            mActivityView.startActivity(mBubbleIntent, options);
+                        } catch (RuntimeException e) {
+                            // If there's a runtime exception here then there's something
+                            // wrong with the intent, we can't really recover / try to populate
+                            // the bubble again so we'll just remove it.
+                            Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+                                    + ", " + e.getMessage() + "; removing bubble");
+                            mBubbleController.removeBubble(mBubble.getKey(),
+                                    BubbleController.DISMISS_INVALID_INTENT);
+                        }
+                    });
+                    mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
             }
         }
 
         @Override
         public void onActivityViewDestroyed(ActivityView view) {
-            mActivityViewReady = false;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            mActivityViewStatus = ActivityViewStatus.RELEASED;
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskCreated: taskId=" + taskId
+                        + " bubble=" + getBubbleKey());
+            }
+            // Since Bubble ActivityView applies singleTaskDisplay this is
+            // guaranteed to only be called once per ActivityView. The taskId is
+            // saved to use for removeTask, preventing appearance in recent tasks.
+            mTaskId = taskId;
         }
 
         /**
@@ -125,9 +173,14 @@
          */
         @Override
         public void onTaskRemovalStarted(int taskId) {
-            if (mEntry != null) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+                        + " mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            if (mBubble != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mEntry.key,
+                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
@@ -149,20 +202,22 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mPm = context.getPackageManager();
-        mMinHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
-        try {
-            mNotificationManagerService = INotificationManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
-        } catch (ServiceManager.ServiceNotFoundException e) {
-            Log.w(TAG, e);
-        }
+        mDisplaySize = new Point();
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+        Resources res = getResources();
+        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+        mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
+        }
 
         Resources res = getResources();
         mPointerView = findViewById(R.id.pointer_view);
@@ -173,10 +228,10 @@
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
         mPointerView.setBackground(mPointerDrawable);
-        mPointerView.setVisibility(GONE);
+        mPointerView.setVisibility(INVISIBLE);
 
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_header_height);
+                R.dimen.bubble_settings_size);
         mSettingsIcon = findViewById(R.id.settings_button);
         mSettingsIcon.setOnClickListener(this);
 
@@ -210,6 +265,10 @@
         });
     }
 
+    private String getBubbleKey() {
+        return mBubble != null ? mBubble.getKey() : "null";
+    }
+
     void applyThemeAttrs() {
         TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
         int bgColor = ta.getColor(
@@ -235,6 +294,9 @@
         if (mActivityView != null) {
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
         }
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
+        }
     }
 
     /**
@@ -246,6 +308,10 @@
      * and setting {@code false} actually means rendering the contents in transparent.
      */
     void setContentVisibility(boolean visibility) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setContentVisibility: visibility=" + visibility
+                    + " bubble=" + getBubbleKey());
+        }
         final float alpha = visibility ? 1f : 0f;
         mPointerView.setAlpha(alpha);
         if (mActivityView != null) {
@@ -259,44 +325,37 @@
      */
     void updateInsets(WindowInsets insets) {
         if (usingActivityView()) {
-            Point displaySize = new Point();
-            mActivityView.getContext().getDisplay().getSize(displaySize);
-            int[] windowLocation = mActivityView.getLocationOnScreen();
-            final int windowBottom = windowLocation[1] + mActivityView.getHeight();
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
-            final int insetsBottom = Math.max(0,
-                    windowBottom + keyboardHeight - displaySize.y);
+            int[] screenLoc = mActivityView.getLocationOnScreen();
+            final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
+            final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(),
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetBottom()
+                            : 0);
+            final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0);
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
         }
     }
 
     /**
-     * Sets the listener to notify when a bubble has been blocked.
+     * Sets the bubble used to populate this view.
      */
-    public void setOnBlockedListener(OnBubbleBlockedListener listener) {
-        mOnBubbleBlockedListener = listener;
-    }
+    public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
 
-    /**
-     * Sets the notification entry used to populate this view.
-     */
-    public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
         mStackView = stackView;
-        mEntry = entry;
+        mBubble = bubble;
         mAppName = appName;
 
-        ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                    entry.notification.getPackageName(),
+            ApplicationInfo info = mPm.getApplicationInfo(
+                    bubble.getPackageName(),
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                mAppIcon = mPm.getApplicationIcon(info);
-            }
+            mAppIcon = mPm.getApplicationIcon(info);
         } catch (PackageManager.NameNotFoundException e) {
             // Do nothing.
         }
@@ -312,52 +371,46 @@
      * Lets activity view know it should be shown / populated.
      */
     public void populateExpandedView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "populateExpandedView: "
+                    + "bubble=" + getBubbleKey());
+        }
+
         if (usingActivityView()) {
             mActivityView.setCallback(mStateCallback);
         } else {
-            // We're using notification template
-            ViewGroup parent = (ViewGroup) mNotifRow.getParent();
-            if (parent == this) {
-                // Already added
-                return;
-            } else if (parent != null) {
-                // Still in the shade... remove it
-                parent.removeView(mNotifRow);
-            }
-            addView(mNotifRow, 1 /* index */);
-            mPointerView.setAlpha(1f);
+            Log.e(TAG, "Cannot populate expanded view.");
         }
     }
 
     /**
-     * Updates the entry backing this view. This will not re-populate ActivityView, it will
+     * Updates the bubble backing this view. This will not re-populate ActivityView, it will
      * only update the deep-links in the title, and the height of the view.
      */
-    public void update(NotificationEntry entry) {
-        if (entry.key.equals(mEntry.key)) {
-            mEntry = entry;
+    public void update(Bubble bubble) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
+        if (bubble.getKey().equals(mBubble.getKey())) {
+            mBubble = bubble;
             updateSettingsContentDescription();
             updateHeight();
         } else {
-            Log.w(TAG, "Trying to update entry with different key, new entry: "
-                    + entry.key + " old entry: " + mEntry.key);
+            Log.w(TAG, "Trying to update entry with different key, new bubble: "
+                    + bubble.getKey() + " old bubble: " + bubble.getKey());
         }
     }
 
     private void updateExpandedView() {
-        mBubbleIntent = getBubbleIntent(mEntry);
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateExpandedView: bubble="
+                    + getBubbleKey());
+        }
+
+        mBubbleIntent = mBubble.getBubbleIntent(mContext);
         if (mBubbleIntent != null) {
-            if (mNotifRow != null) {
-                // Clear out the row if we had it previously
-                removeView(mNotifRow);
-                mNotifRow = null;
-            }
             setContentVisibility(false);
             mActivityView.setVisibility(VISIBLE);
-        } else if (DEBUG_ENABLE_AUTO_BUBBLE) {
-            // Hide activity view if we had it previously
-            mActivityView.setVisibility(GONE);
-            mNotifRow = mEntry.getRow();
         }
         updateView();
     }
@@ -371,29 +424,12 @@
     }
 
     void updateHeight() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
+        }
         if (usingActivityView()) {
-            Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
-            float desiredHeight;
-            if (data == null) {
-                // This is a contentIntent based bubble, lets allow it to be the max height
-                // as it was forced into this mode and not prepared to be small
-                desiredHeight = mStackView.getMaxExpandedHeight();
-            } else {
-                boolean useRes = data.getDesiredHeightResId() != 0;
-                float desiredPx;
-                if (useRes) {
-                    desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
-                            mEntry.notification.getPackageName(),
-                            mEntry.notification.getUser().getIdentifier());
-                } else {
-                    desiredPx = data.getDesiredHeight()
-                            * getContext().getResources().getDisplayMetrics().density;
-                }
-                desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
-            }
-            int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
-                    - mPointerMargin;
-            float height = Math.min(desiredHeight, max);
+            float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
+            float height = Math.min(desiredHeight, getMaxExpandedHeight());
             height = Math.max(height, mMinHeight);
             LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
             mNeedsNewHeight =  lp.height != height;
@@ -401,28 +437,66 @@
                 // If the keyboard is visible... don't adjust the height because that will cause
                 // a configuration change and the keyboard will be lost.
                 lp.height = (int) height;
-                mBubbleHeight = (int) height;
                 mActivityView.setLayoutParams(lp);
                 mNeedsNewHeight = false;
             }
-        } else {
-            mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
+                        + " mNeedsNewHeight=" + mNeedsNewHeight);
+            }
         }
     }
 
+    private int getMaxExpandedHeight() {
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+        int[] windowLocation = mActivityView.getLocationOnScreen();
+        int bottomInset = getRootWindowInsets() != null
+                ? getRootWindowInsets().getStableInsetBottom()
+                : 0;
+        return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
+                - mPointerMargin - bottomInset;
+    }
+
+    /**
+     * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
+     * expanded view.
+     *
+     * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
+     */
+    boolean intersectingTouchableContent(int rawX, int rawY) {
+        mTempRect.setEmpty();
+        if (mActivityView != null) {
+            mTempLoc = mActivityView.getLocationOnScreen();
+            mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
+                    mTempLoc[1] - mExpandedViewTouchSlop,
+                    mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
+                    mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
+        }
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        mTempLoc = mSettingsIcon.getLocationOnScreen();
+        mTempRect.set(mTempLoc[0],
+                mTempLoc[1],
+                mTempLoc[0] + mSettingsIcon.getWidth(),
+                mTempLoc[1] + mSettingsIcon.getHeight());
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void onClick(View view) {
-        if (mEntry == null) {
+        if (mBubble == null) {
             return;
         }
-        Notification n = mEntry.notification.getNotification();
         int id = view.getId();
         if (id == R.id.settings_button) {
-            Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
-                    mEntry.notification.getUid());
+            Intent intent = mBubble.getSettingsIntent();
             mStackView.collapseStack(() -> {
-                mContext.startActivityAsUser(intent, mEntry.notification.getUser());
-                logBubbleClickEvent(mEntry,
+                mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser());
+                logBubbleClickEvent(mBubble,
                         StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
             });
         }
@@ -442,13 +516,14 @@
      * Update appearance of the expanded view being displayed.
      */
     public void updateView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateView: bubble="
+                    + getBubbleKey());
+        }
         if (usingActivityView()
                 && mActivityView.getVisibility() == VISIBLE
                 && mActivityView.isAttachedToWindow()) {
             mActivityView.onLocationChanged();
-        } else if (mNotifRow != null) {
-            applyRowState(mNotifRow);
-            mPointerView.setAlpha(1f);
         }
         updateHeight();
     }
@@ -467,17 +542,44 @@
      * Removes and releases an ActivityView if one was previously created for this bubble.
      */
     public void cleanUpExpandedState() {
-        removeView(mNotifRow);
-
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
+                    + ", bubble=" + getBubbleKey());
+        }
         if (mActivityView == null) {
             return;
         }
-        if (mActivityViewReady) {
-            mActivityView.release();
+        switch (mActivityViewStatus) {
+            case INITIALIZED:
+            case ACTIVITY_STARTED:
+                mActivityView.release();
+        }
+        if (mTaskId != -1) {
+            try {
+                ActivityTaskManager.getService().removeTask(mTaskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove taskId " + mTaskId);
+            }
+            mTaskId = -1;
         }
         removeView(mActivityView);
+
         mActivityView = null;
-        mActivityViewReady = false;
+    }
+
+    /**
+     * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+     * which {@link ActivityView} uses.
+     */
+    void notifyDisplayEmpty() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "notifyDisplayEmpty: bubble="
+                    + getBubbleKey()
+                    + " mActivityViewStatus=" + mActivityViewStatus);
+        }
+        if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+            mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+        }
     }
 
     private boolean usingActivityView() {
@@ -494,76 +596,14 @@
         return INVALID_DISPLAY;
     }
 
-    private void applyRowState(ExpandableNotificationRow view) {
-        view.reset();
-        view.setHeadsUp(false);
-        view.resetTranslation();
-        view.setOnKeyguard(false);
-        view.setClipBottomAmount(0);
-        view.setClipTopAmount(0);
-        view.setContentTransformationAmount(0, false);
-        view.setIconsVisible(true);
-
-        // TODO - Need to reset this (and others) when view goes back in shade, leave for now
-        // view.setTopRoundness(1, false);
-        // view.setBottomRoundness(1, false);
-
-        ExpandableViewState viewState = view.getViewState();
-        viewState = viewState == null ? new ExpandableViewState() : viewState;
-        viewState.height = view.getIntrinsicHeight();
-        viewState.gone = false;
-        viewState.hidden = false;
-        viewState.dimmed = false;
-        viewState.alpha = 1f;
-        viewState.notGoneIndex = -1;
-        viewState.xTranslation = 0;
-        viewState.yTranslation = 0;
-        viewState.zTranslation = 0;
-        viewState.scaleX = 1;
-        viewState.scaleY = 1;
-        viewState.inShelf = true;
-        viewState.headsUpIsVisible = false;
-        viewState.applyToView(view);
-    }
-
-    private Intent getSettingsIntent(String packageName, final int appUid) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        return intent;
-    }
-
-    @Nullable
-    private PendingIntent getBubbleIntent(NotificationEntry entry) {
-        Notification notif = entry.notification.getNotification();
-        Notification.BubbleMetadata data = notif.getBubbleMetadata();
-        if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) {
-            return data.getIntent();
-        }
-        return null;
-    }
-
-    /**
-     * Listener that is notified when a bubble is blocked.
-     */
-    public interface OnBubbleBlockedListener {
-        /**
-         * Called when a bubble is blocked for the provided entry.
-         */
-        void onBubbleBlocked(NotificationEntry entry);
-    }
-
     /**
      * Logs bubble UI click event.
      *
-     * @param entry the bubble notification entry that user is interacting with.
+     * @param bubble the bubble notification entry that user is interacting with.
      * @param action the user interaction enum.
      */
-    private void logBubbleClickEvent(NotificationEntry entry, int action) {
-        StatusBarNotification notification = entry.notification;
+    private void logBubbleClickEvent(Bubble bubble, int action) {
+        StatusBarNotification notification = bubble.getEntry().notification;
         StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                 notification.getPackageName(),
                 notification.getNotification().getChannelId(),
@@ -573,27 +613,8 @@
                 action,
                 mStackView.getNormalizedXPosition(),
                 mStackView.getNormalizedYPosition(),
-                entry.showInShadeWhenBubble(),
-                entry.isForegroundService(),
-                BubbleController.isForegroundApp(mContext, notification.getPackageName()));
-    }
-
-    private int getDimenForPackageUser(int resId, String pkg, int userId) {
-        Resources r;
-        if (pkg != null) {
-            try {
-                if (userId == UserHandle.USER_ALL) {
-                    userId = UserHandle.USER_SYSTEM;
-                }
-                r = mPm.getResourcesForApplicationAsUser(pkg, userId);
-                return r.getDimensionPixelSize(resId);
-            } catch (PackageManager.NameNotFoundException ex) {
-                // Uninstalled, don't care
-            } catch (Resources.NotFoundException e) {
-                // Invalid res id, return 0 and user our default
-                Log.e(TAG, "Couldn't find desired height res id", e);
-            }
-        }
-        return 0;
+                bubble.showInShadeWhenBubble(),
+                bubble.isOngoing(),
+                false /* isAppForeground (unused) */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 71f68c1..58f3f22 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -39,8 +39,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
@@ -57,6 +56,9 @@
     private final int mFlyoutSpaceFromBubble;
     private final int mPointerSize;
     private final int mBubbleSize;
+    private final int mBubbleIconBitmapSize;
+    private final float mBubbleIconTopPadding;
+
     private final int mFlyoutElevation;
     private final int mBubbleElevation;
     private final int mFloatingBackgroundColor;
@@ -64,14 +66,11 @@
 
     private final ViewGroup mFlyoutTextContainer;
     private final TextView mFlyoutText;
-    /** Spring animation for the flyout. */
-    private final SpringAnimation mFlyoutSpring =
-            new SpringAnimation(this, DynamicAnimation.TRANSLATION_X);
 
     /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
     private final float mNewDotRadius;
     private final float mNewDotSize;
-    private final float mNewDotOffsetFromBubbleBounds;
+    private final float mOriginalDotSize;
 
     /**
      * The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -113,7 +112,6 @@
      */
     private float mFlyoutToDotWidthDelta = 0f;
     private float mFlyoutToDotHeightDelta = 0f;
-    private float mFlyoutToDotCornerRadiusDelta;
 
     /** The translation values when the flyout is completely transitioned into the dot. */
     private float mTranslationXWhenDot = 0f;
@@ -126,11 +124,18 @@
     private float mBgTranslationX;
     private float mBgTranslationY;
 
+    private float[] mDotCenter;
+
     /** The flyout's X translation when at rest (not animating or dragging). */
     private float mRestingTranslationX = 0f;
 
+    /** The badge sizes are defined as percentages of the app icon size. Same value as Launcher3. */
+    private static final float SIZE_PERCENTAGE = 0.228f;
+
+    private static final float DOT_SCALE = 1f;
+
     /** Callback to run when the flyout is hidden. */
-    private Runnable mOnHide;
+    @Nullable private Runnable mOnHide;
 
     public BubbleFlyoutView(Context context) {
         super(context);
@@ -143,11 +148,16 @@
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
         mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
         mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubbleIconTopPadding  = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
+
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
-        mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
-        mNewDotRadius = BadgeRenderer.getDotRadius(mNewDotOffsetFromBubbleBounds);
+
+        mOriginalDotSize = SIZE_PERCENTAGE * mBubbleIconBitmapSize;
+        mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
         mNewDotSize = mNewDotRadius * 2f;
 
         final TypedArray ta = mContext.obtainStyledAttributes(
@@ -156,7 +166,6 @@
                         android.R.attr.dialogCornerRadius});
         mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
         mCornerRadius = ta.getDimensionPixelSize(1, 0);
-        mFlyoutToDotCornerRadiusDelta = mNewDotRadius - mCornerRadius;
         ta.recycle();
 
         // Add padding for the pointer on either side, onDraw will draw it in this space.
@@ -193,17 +202,17 @@
         super.onDraw(canvas);
     }
 
-    /** Configures the flyout and animates it in. */
-    void showFlyout(
+    /** Configures the flyout, collapsed into to dot form. */
+    void setupFlyoutStartingAsDot(
             CharSequence updateMessage, PointF stackPos, float parentWidth,
-            boolean arrowPointingLeft, int dotColor, Runnable onHide) {
+            boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
+            @Nullable Runnable onHide, float[] dotCenter) {
         mArrowPointingLeft = arrowPointingLeft;
         mDotColor = dotColor;
         mOnHide = onHide;
+        mDotCenter = dotCenter;
 
-        setCollapsePercent(0f);
-        setAlpha(0f);
-        setVisibility(VISIBLE);
+        setCollapsePercent(1f);
 
         // Set the flyout TextView's max width in terms of percent, and then subtract out the
         // padding so that the entire flyout view will be the desired width (rather than the
@@ -214,14 +223,16 @@
 
         // Wait for the TextView to lay out so we know its line count.
         post(() -> {
+            float restingTranslationY;
             // Multi line flyouts get top-aligned to the bubble.
             if (mFlyoutText.getLineCount() > 1) {
-                setTranslationY(stackPos.y);
+                restingTranslationY = stackPos.y + mBubbleIconTopPadding;
             } else {
                 // Single line flyouts are vertically centered with respect to the bubble.
-                setTranslationY(
-                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f);
+                restingTranslationY =
+                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
             }
+            setTranslationY(restingTranslationY);
 
             // Calculate the translation required to position the flyout next to the bubble stack,
             // with the desired padding.
@@ -229,40 +240,30 @@
                     ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
                     : stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
 
-            // Translate towards the stack slightly.
-            setTranslationX(
-                    mRestingTranslationX + (arrowPointingLeft ? -mBubbleSize : mBubbleSize));
-
-            // Fade in the entire flyout and spring it to its normal position.
-            animate().alpha(1f);
-            mFlyoutSpring.animateToFinalPosition(mRestingTranslationX);
-
             // Calculate the difference in size between the flyout and the 'dot' so that we can
             // transform into the dot later.
             mFlyoutToDotWidthDelta = getWidth() - mNewDotSize;
             mFlyoutToDotHeightDelta = getHeight() - mNewDotSize;
 
             // Calculate the translation values needed to be in the correct 'new dot' position.
-            final float distanceFromFlyoutLeftToDotCenterX =
-                    mFlyoutSpaceFromBubble + mNewDotOffsetFromBubbleBounds / 2;
-            if (mArrowPointingLeft) {
-                mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
-            } else {
-                mTranslationXWhenDot =
-                        getWidth() + distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
-            }
+            final float dotPositionX = stackPos.x + mDotCenter[0] - (mOriginalDotSize / 2f);
+            final float dotPositionY = stackPos.y + mDotCenter[1] - (mOriginalDotSize / 2f);
 
-            mTranslationYWhenDot =
-                    getHeight() / 2f
-                            - mNewDotRadius
-                            - mBubbleSize / 2f
-                            + mNewDotOffsetFromBubbleBounds / 2;
+            final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
+            final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
+
+            mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
+            mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
+            if (onLayoutComplete != null) {
+                onLayoutComplete.run();
+            }
         });
     }
 
     /**
-     * Hides the flyout and runs the optional callback passed into showFlyout. The flyout has been
-     * animated into the 'new' dot by the time we call this, so no animations are needed.
+     * Hides the flyout and runs the optional callback passed into setupFlyoutStartingAsDot.
+     * The flyout has been animated into the 'new' dot by the time we call this, so no animations
+     * are needed.
      */
     void hideFlyout() {
         if (mOnHide != null) {
@@ -275,6 +276,13 @@
 
     /** Sets the percentage that the flyout should be collapsed into dot form. */
     void setCollapsePercent(float percentCollapsed) {
+        // This is unlikely, but can happen in a race condition where the flyout view hasn't been
+        // laid out and returns 0 for getWidth(). We check for this condition at the sites where
+        // this method is called, but better safe than sorry.
+        if (Float.isNaN(percentCollapsed)) {
+            return;
+        }
+
         mPercentTransitionedToDot = Math.max(0f, Math.min(percentCollapsed, 1f));
         mPercentStillFlyout = (1f - mPercentTransitionedToDot);
 
@@ -311,8 +319,8 @@
         // percentage.
         final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot);
         final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot);
-        final float cornerRadius = mCornerRadius
-                - (mFlyoutToDotCornerRadiusDelta * mPercentTransitionedToDot);
+        final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+                + mCornerRadius * (1 - mPercentTransitionedToDot);
 
         // Translate the flyout background towards the collapsed 'dot' state.
         mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot;
@@ -336,7 +344,7 @@
         canvas.save();
         canvas.translate(mBgTranslationX, mBgTranslationY);
         renderPointerTriangle(canvas, width, height);
-        canvas.drawRoundRect(mBgRect, cornerRadius, cornerRadius, mBgPaint);
+        canvas.drawRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, mBgPaint);
         canvas.restore();
     }
 
@@ -379,7 +387,10 @@
         if (!mTriangleOutline.isEmpty()) {
             // Draw the rect into the outline as a path so we can merge the triangle path into it.
             final Path rectPath = new Path();
-            rectPath.addRoundRect(mBgRect, mCornerRadius, mCornerRadius, Path.Direction.CW);
+            final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+                    + mCornerRadius * (1 - mPercentTransitionedToDot);
+            rectPath.addRoundRect(mBgRect, interpolatedRadius,
+                    interpolatedRadius, Path.Direction.CW);
             outline.setConvexPath(rectPath);
 
             // Get rid of the triangle path once it has disappeared behind the flyout.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
new file mode 100644
index 0000000..a1c77c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.systemui.R;
+
+/**
+ * Factory for creating normalized bubble icons.
+ * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
+ * so there is no need to manage a pool across multiple threads.
+ */
+public class BubbleIconFactory extends BaseIconFactory {
+    protected BubbleIconFactory(Context context) {
+        super(context, context.getResources().getConfiguration().densityDpi,
+                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
+    }
+
+    public int getBadgeSize() {
+        return mContext.getResources().getDimensionPixelSize(
+                com.android.launcher3.icons.R.dimen.profile_badge_size);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f87bcef..31cf853 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,16 +19,20 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -41,11 +45,11 @@
 import android.util.Log;
 import android.util.StatsLog;
 import android.view.Choreographer;
+import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -69,6 +73,8 @@
 import com.android.systemui.bubbles.animation.StackAnimationController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
@@ -79,8 +85,7 @@
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
 public class BubbleStackView extends FrameLayout {
-    private static final String TAG = "BubbleStackView";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
     /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
     static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -160,9 +165,14 @@
     private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
     private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
+    /**
+     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
+     * previous one animates out.
+     */
+    private Runnable mFlyoutOnHide;
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
-    private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
+    private OnLayoutChangeListener mOrientationChangedListener;
     /** Whether the stack was on the left side of the screen prior to rotation. */
     private boolean mWasOnLeftBeforeRotation = false;
     /**
@@ -172,18 +182,17 @@
     private float mVerticalPosPercentBeforeRotation = -1;
 
     private int mBubbleSize;
-    private int mBubblePadding;
+    private int mBubblePaddingTop;
+    private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
     private int mExpandedAnimateXDistance;
     private int mExpandedAnimateYDistance;
     private int mPointerHeight;
     private int mStatusBarHeight;
-    private int mPipDismissHeight;
     private int mImeOffset;
-
+    private BubbleIconFactory mBubbleIconFactory;
     private Bubble mExpandedBubble;
     private boolean mIsExpanded;
-    private boolean mImeVisible;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
     private boolean mStackOnLeftOrWillBe = false;
@@ -191,9 +200,20 @@
     /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
     private boolean mIsGestureInProgress = false;
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Stack view state:");
+        pw.print("  gestureInProgress:    "); pw.println(mIsGestureInProgress);
+        pw.print("  showingDismiss:       "); pw.println(mShowingDismiss);
+        pw.print("  isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
+        pw.print("  draggingInDismiss:    "); pw.println(mDraggingInDismissTarget);
+        pw.print("  animatingMagnet:      "); pw.println(mAnimatingMagnet);
+        mStackAnimationController.dump(fd, pw, args);
+        mExpandedAnimationController.dump(fd, pw, args);
+    }
+
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
-    private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
 
     private boolean mViewUpdatedRequested = false;
     private boolean mIsExpansionAnimating = false;
@@ -225,7 +245,7 @@
                 @Override
                 public boolean onPreDraw() {
                     getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
-                    applyCurrentState();
+                    updateExpandedView();
                     mViewUpdatedRequested = false;
                     return true;
                 }
@@ -270,6 +290,11 @@
     private float mFlyoutDragDeltaX = 0f;
 
     /**
+     * Runnable that animates in the flyout. This reference is needed to cancel delayed postings.
+     */
+    private Runnable mAnimateInFlyout;
+
+    /**
      * End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
      * it immediately.
      */
@@ -282,13 +307,13 @@
                 }
             };
 
-    @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+    @NonNull
+    private final SurfaceSynchronizer mSurfaceSynchronizer;
 
     private BubbleDismissView mDismissContainer;
     private Runnable mAfterMagnet;
 
-    private boolean mSuppressNewDot = false;
-    private boolean mSuppressFlyout = false;
+    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer) {
@@ -302,7 +327,8 @@
 
         Resources res = getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+        mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
         mExpandedAnimateXDistance =
                 res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
         mExpandedAnimateYDistance =
@@ -311,13 +337,12 @@
 
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.pip_dismiss_gradient_height);
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
 
         mDisplaySize = new Point();
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getSize(mDisplaySize);
+        // We use the real size & subtract screen decorations / window insets ourselves when needed
+        wm.getDefaultDisplay().getRealSize(mDisplaySize);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
 
@@ -325,8 +350,9 @@
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
         mStackAnimationController = new StackAnimationController();
+
         mExpandedAnimationController = new ExpandedAnimationController(
-                mDisplaySize, mExpandedViewPadding);
+                mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
         mBubbleContainer = new PhysicsAnimationLayout(context);
@@ -335,6 +361,8 @@
         mBubbleContainer.setClipChildren(false);
         addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
+        mBubbleIconFactory = new BubbleIconFactory(context);
+
         mExpandedViewContainer = new FrameLayout(context);
         mExpandedViewContainer.setElevation(elevation);
         mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
@@ -342,15 +370,9 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
-        mFlyout = new BubbleFlyoutView(context);
-        mFlyout.setVisibility(GONE);
-        mFlyout.animate()
-                .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
-                .setInterpolator(new AccelerateDecelerateInterpolator());
-        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
+        setUpFlyout();
         mFlyoutTransitionSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setStiffness(SpringForce.STIFFNESS_LOW)
                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
 
@@ -361,13 +383,6 @@
                 Gravity.BOTTOM));
         addView(mDismissContainer);
 
-        mDismissContainer = new BubbleDismissView(mContext);
-        mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
-                MATCH_PARENT,
-                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
-                Gravity.BOTTOM));
-        addView(mDismissContainer);
-
         mExpandedViewXAnim =
                 new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
         mExpandedViewXAnim.setSpring(
@@ -383,7 +398,7 @@
                         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
             if (mIsExpanded && mExpandedBubble != null) {
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             }
         });
 
@@ -392,34 +407,58 @@
         mBubbleContainer.bringToFront();
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
             if (!mIsExpanded || mIsExpansionAnimating) {
                 return view.onApplyWindowInsets(insets);
             }
-            mImeVisible = keyboardHeight != 0;
-
-            float newY = getYPositionForExpandedView();
-            if (newY < 0) {
-                // TODO: This means our expanded content is too big to fit on screen. Right now
-                // we'll let it translate off but we should be clipping it & pushing the header
-                // down so that it always remains visible.
-            }
-            mExpandedViewYAnim.animateToFinalPosition(newY);
             mExpandedAnimationController.updateYPosition(
                     // Update the insets after we're done translating otherwise position
                     // calculation for them won't be correct.
-                    () -> mExpandedBubble.expandedView.updateInsets(insets));
+                    () -> mExpandedBubble.getExpandedView().updateInsets(insets));
             return view.onApplyWindowInsets(insets);
         });
 
-        mMoveStackToValidPositionOnLayoutListener =
+        mOrientationChangedListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    mExpandedAnimationController.updateOrientation(mOrientation, mDisplaySize);
+                    mStackAnimationController.updateOrientation(mOrientation);
+
+                    // Reposition & adjust the height for new orientation
+                    if (mIsExpanded) {
+                        mExpandedViewContainer.setTranslationY(getExpandedViewY());
+                        mExpandedBubble.getExpandedView().updateView();
+                    }
+
+                    // Need to update the padding around the view
+                    WindowInsets insets = getRootWindowInsets();
+                    int leftPadding = mExpandedViewPadding;
+                    int rightPadding = mExpandedViewPadding;
+                    if (insets != null) {
+                        // Can't have the expanded view overlaying notches
+                        int cutoutLeft = 0;
+                        int cutoutRight = 0;
+                        DisplayCutout cutout = insets.getDisplayCutout();
+                        if (cutout != null) {
+                            cutoutLeft = cutout.getSafeInsetLeft();
+                            cutoutRight = cutout.getSafeInsetRight();
+                        }
+                        // Or overlaying nav or status bar
+                        leftPadding += Math.max(cutoutLeft, insets.getStableInsetLeft());
+                        rightPadding += Math.max(cutoutRight, insets.getStableInsetRight());
+                    }
+                    mExpandedViewContainer.setPadding(leftPadding, mExpandedViewPadding,
+                            rightPadding, mExpandedViewPadding);
+
+                    if (mIsExpanded) {
+                        // Re-draw bubble row and pointer for new orientation.
+                        mExpandedAnimationController.expandFromStack(() -> {
+                            updatePointerPosition();
+                        } /* after */);
+                    }
                     if (mVerticalPosPercentBeforeRotation >= 0) {
                         mStackAnimationController.moveStackToSimilarPositionAfterRotation(
                                 mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
                     }
-                    removeOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
+                    removeOnLayoutChangeListener(mOrientationChangedListener);
                 };
 
         // This must be a separate OnDrawListener since it should be called for every draw.
@@ -449,25 +488,53 @@
         });
     }
 
+    private void setUpFlyout() {
+        if (mFlyout != null) {
+            removeView(mFlyout);
+        }
+        mFlyout = new BubbleFlyoutView(getContext());
+        mFlyout.setVisibility(GONE);
+        mFlyout.animate()
+                .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
+                .setInterpolator(new AccelerateDecelerateInterpolator());
+        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+    }
+
     /**
      * Handle theme changes.
      */
     public void onThemeChanged() {
+        // Recreate icon factory to update default adaptive icon scale.
+        mBubbleIconFactory = new BubbleIconFactory(mContext);
+        setUpFlyout();
         for (Bubble b: mBubbleData.getBubbles()) {
-            b.iconView.updateViews();
-            b.expandedView.applyThemeAttrs();
+            b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+            b.getIconView().updateViews();
+            b.getExpandedView().applyThemeAttrs();
         }
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
-    public void onOrientationChanged() {
+    public void onOrientationChanged(int orientation) {
+        mOrientation = orientation;
+
+        // Display size is based on the rotation device was in when requested, we should update it
+        // We use the real size & subtract screen decorations / window insets ourselves when needed
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getRealSize(mDisplaySize);
+
+        // Some resources change depending on orientation
+        Resources res = getContext().getResources();
+        mStatusBarHeight = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+
         final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
         mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
         mVerticalPosPercentBeforeRotation =
                 (mStackAnimationController.getStackPosition().y - allowablePos.top)
                         / (allowablePos.bottom - allowablePos.top);
-        addOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
-
+        addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
     }
 
@@ -483,18 +550,6 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        float x = ev.getRawX();
-        float y = ev.getRawY();
-        // If we're expanded only intercept if the tap is outside of the widget container
-        if (mIsExpanded && isIntersecting(mExpandedViewContainer, x, y)) {
-            return false;
-        } else {
-            return isIntersecting(mBubbleContainer, x, y);
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
 
@@ -570,7 +625,7 @@
         }
         Bubble topBubble = mBubbleData.getBubbles().get(0);
         String appName = topBubble.getAppName();
-        Notification notification = topBubble.entry.notification.getNotification();
+        Notification notification = topBubble.getEntry().notification.getNotification();
         CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         String titleStr = getResources().getString(R.string.stream_notification);
         if (titleCharSeq != null) {
@@ -616,6 +671,7 @@
 
     /**
      * Updates the visibility of the 'dot' indicating an update on the bubble.
+     *
      * @param key the {@link NotificationEntry#key} associated with the bubble.
      */
     public void updateDotVisibility(String key) {
@@ -640,10 +696,17 @@
     }
 
     /**
+     * Whether the stack of bubbles is animating to or from expansion.
+     */
+    public boolean isExpansionAnimating() {
+        return mIsExpansionAnimating;
+    }
+
+    /**
      * The {@link BubbleView} that is expanded, null if one does not exist.
      */
     BubbleView getExpandedBubbleView() {
-        return mExpandedBubble != null ? mExpandedBubble.iconView : null;
+        return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
     }
 
     /**
@@ -664,36 +727,33 @@
         Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
         if (bubbleToExpand != null) {
             setSelectedBubble(bubbleToExpand);
-            bubbleToExpand.entry.setShowInShadeWhenBubble(false);
+            bubbleToExpand.setShowInShadeWhenBubble(false);
             setExpanded(true);
         }
     }
 
-    /**
-     * Sets the entry that should be expanded and expands if needed.
-     */
-    @VisibleForTesting
-    void setExpandedBubble(NotificationEntry entry) {
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            if (entry.equals(bv.getEntry())) {
-                setExpandedBubble(entry.key);
-            }
-        }
-    }
-
     // via BubbleData.Listener
     void addBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "addBubble: " + bubble);
         }
-        bubble.inflate(mInflater, this);
-        mBubbleContainer.addView(bubble.iconView, 0,
-                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
-        if (bubble.iconView != null) {
-            bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */);
+
+        if (mBubbleContainer.getChildCount() == 0) {
+            mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
+
+        bubble.inflate(mInflater, this);
+        bubble.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+        bubble.getIconView().updateViews();
+
+        // Set the dot position to the opposite of the side the stack is resting on, since the stack
+        // resting slightly off-screen would result in the dot also being off-screen.
+        bubble.getIconView().setDotPosition(
+                !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+
+        mBubbleContainer.addView(bubble.getIconView(), 0,
+                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -702,13 +762,14 @@
 
     // via BubbleData.Listener
     void removeBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "removeBubble: " + bubble);
         }
         // Remove it from the views
-        int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
+        int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView());
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
+            bubble.cleanupExpandedState();
             logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -726,8 +787,10 @@
     public void updateBubbleOrder(List<Bubble> bubbles) {
         for (int i = 0; i < bubbles.size(); i++) {
             Bubble bubble = bubbles.get(i);
-            mBubbleContainer.reorderView(bubble.iconView, i);
+            mBubbleContainer.reorderView(bubble.getIconView(), i);
         }
+
+        updateBubbleZOrdersAndDotPosition(false /* animate */);
     }
 
     /**
@@ -737,7 +800,7 @@
      */
     // via BubbleData.Listener
     public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
         }
         if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
@@ -760,9 +823,8 @@
                 requestUpdate();
                 logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                 logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-                notifyExpansionChanged(previouslySelected.entry, false /* expanded */);
-                notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.entry,
-                        true /* expanded */);
+                notifyExpansionChanged(previouslySelected, false /* expanded */);
+                notifyExpansionChanged(bubbleToSelect, true /* expanded */);
             });
         }
     }
@@ -774,42 +836,32 @@
      */
     // via BubbleData.Listener
     public void setExpanded(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setExpanded: " + shouldExpand);
         }
-        boolean wasExpanded = mIsExpanded;
-        if (shouldExpand == wasExpanded) {
+        if (shouldExpand == mIsExpanded) {
             return;
         }
-        if (wasExpanded) {
-            // Collapse the stack
-            mExpandedViewContainer.setAlpha(0.0f);
-            // TODO: In order to prevent flicker, code below should be executed after the alpha
-            // value set on the mExpandedViewContainer is reflected on the screen. However, we
-            // cannot just postpone the execution like #setSelectedBubble(), since some of member
-            // variables referred by the code are overridden before the execution.
-            if (mExpandedBubble != null) {
-                mExpandedBubble.setContentVisibility(false);
-            }
-            animateExpansion(false /* expand */);
+        if (mIsExpanded) {
+            animateCollapse();
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
-            // Expand the stack
-            animateExpansion(true /* expand */);
+            animateExpansion();
             // TODO: move next line to BubbleData
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
         }
-        notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded);
+        notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
 
     /**
      * Dismiss the stack of bubbles.
+     *
      * @deprecated
      */
     @Deprecated
     void stackDismissed(int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "stackDismissed: reason=" + reason);
         }
         mBubbleData.dismissAll(reason);
@@ -826,21 +878,23 @@
         float y = event.getRawY();
         if (mIsExpanded) {
             if (isIntersecting(mBubbleContainer, x, y)) {
+                // Could be tapping or dragging a bubble while expanded
                 for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
                     BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i);
                     if (isIntersecting(view, x, y)) {
                         return view;
                     }
                 }
-            } else if (isIntersecting(mExpandedViewContainer, x, y)) {
-                return mExpandedViewContainer;
             }
-            // Outside parts of view we care about.
+            BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
+            if (bev.intersectingTouchableContent((int) x, (int) y)) {
+                return bev;
+            }
+            // Outside of the parts we care about.
             return null;
         } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
             return mFlyout;
         }
-
         // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
         return this;
     }
@@ -859,7 +913,7 @@
     @Deprecated
     @MainThread
     void collapseStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack()");
         }
         mBubbleData.setExpanded(false);
@@ -871,7 +925,7 @@
     @Deprecated
     @MainThread
     void collapseStack(Runnable endRunnable) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack(endRunnable)");
         }
         collapseStack();
@@ -889,73 +943,83 @@
     @Deprecated
     @MainThread
     void expandStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "expandStack()");
         }
         mBubbleData.setExpanded(true);
     }
 
-    /**
-     * Tell the stack to animate to collapsed or expanded state.
-     */
-    private void animateExpansion(boolean shouldExpand) {
-        if (DEBUG) {
-            Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
-        }
-        if (mIsExpanded != shouldExpand) {
-            hideFlyoutImmediate();
-
-            mIsExpanded = shouldExpand;
-            updateExpandedBubble();
-            applyCurrentState();
-
-            mIsExpansionAnimating = true;
-
-            Runnable updateAfter = () -> {
-                applyCurrentState();
-                mIsExpansionAnimating = false;
-                requestUpdate();
-            };
-
-            if (shouldExpand) {
-                mBubbleContainer.setActiveController(mExpandedAnimationController);
-                mExpandedAnimationController.expandFromStack(() -> {
-                    updatePointerPosition();
-                    updateAfter.run();
-                } /* after */);
-            } else {
-                mBubbleContainer.cancelAllAnimations();
-                mExpandedAnimationController.collapseBackToStack(
-                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
-                        () -> {
-                            mBubbleContainer.setActiveController(mStackAnimationController);
-                            updateAfter.run();
-                        });
-            }
-
-            final float xStart =
-                    mStackAnimationController.getStackPosition().x < getWidth() / 2
-                            ? -mExpandedAnimateXDistance
-                            : mExpandedAnimateXDistance;
-
-            final float yStart = Math.min(
-                    mStackAnimationController.getStackPosition().y,
-                    mExpandedAnimateYDistance);
-            final float yDest = getYPositionForExpandedView();
-
-            if (shouldExpand) {
-                mExpandedViewContainer.setTranslationX(xStart);
-                mExpandedViewContainer.setTranslationY(yStart);
-            }
-
-            mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
-            mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
-        }
+    private void beforeExpandedViewAnimation() {
+        hideFlyoutImmediate();
+        updateExpandedBubble();
+        updateExpandedView();
+        mIsExpansionAnimating = true;
     }
 
-    private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
-        if (mExpandListener != null) {
-            mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+    private void afterExpandedViewAnimation() {
+        updateExpandedView();
+        mIsExpansionAnimating = false;
+        requestUpdate();
+    }
+
+    private void animateCollapse() {
+        mIsExpanded = false;
+        final Bubble previouslySelected = mExpandedBubble;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.cancelAllAnimations();
+        mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> {
+                    mBubbleContainer.setActiveController(mStackAnimationController);
+                    afterExpandedViewAnimation();
+                    previouslySelected.setContentVisibility(false);
+                });
+
+        mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
+        mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(0f);
+    }
+
+    private void animateExpansion() {
+        mIsExpanded = true;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.setActiveController(mExpandedAnimationController);
+        mExpandedAnimationController.expandFromStack(() -> {
+            updatePointerPosition();
+            afterExpandedViewAnimation();
+        } /* after */);
+
+
+        mExpandedViewContainer.setTranslationX(getCollapsedX());
+        mExpandedViewContainer.setTranslationY(getCollapsedY());
+        mExpandedViewContainer.setAlpha(0f);
+
+        mExpandedViewXAnim.animateToFinalPosition(0f);
+        mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(1f);
+    }
+
+    private float getCollapsedX() {
+        return mStackAnimationController.getStackPosition().x < getWidth() / 2
+                ? -mExpandedAnimateXDistance
+                : mExpandedAnimateXDistance;
+    }
+
+    private float getCollapsedY() {
+        return Math.min(mStackAnimationController.getStackPosition().y,
+                mExpandedAnimateYDistance);
+    }
+
+    private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+        if (mExpandListener != null && bubble != null) {
+            mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
         }
     }
 
@@ -968,7 +1032,7 @@
 
     /** Moves the bubbles out of the way if they're going to be over the keyboard. */
     public void onImeVisibilityChanged(boolean visible, int height) {
-        mStackAnimationController.setImeHeight(height + mImeOffset);
+        mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
 
         if (!mIsExpanded) {
             mStackAnimationController.animateForImeVisibility(visible);
@@ -977,7 +1041,7 @@
 
     /** Called when a drag operation on an individual bubble has started. */
     public void onBubbleDragStart(View bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
         }
         mExpandedAnimationController.prepareForBubbleDrag(bubble);
@@ -996,7 +1060,7 @@
     /** Called when a drag operation on an individual bubble has finished. */
     public void onBubbleDragFinish(
             View bubble, float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
         }
 
@@ -1005,11 +1069,11 @@
         }
 
         mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
-        springOutDismissTargetAndHideCircle();
+        hideDismissTarget();
     }
 
     void onDragStart() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragStart()");
         }
         if (mIsExpanded || mIsExpansionAnimating) {
@@ -1033,7 +1097,7 @@
     }
 
     void onDragFinish(float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragFinish");
         }
 
@@ -1046,8 +1110,8 @@
                 StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
 
         mStackOnLeftOrWillBe = newStackX <= 0;
-        updateBubbleShadowsAndDotPosition(true /* animate */);
-        springOutDismissTargetAndHideCircle();
+        updateBubbleZOrdersAndDotPosition(true /* animate */);
+        hideDismissTarget();
     }
 
     void onFlyoutDragStart() {
@@ -1055,6 +1119,12 @@
     }
 
     void onFlyoutDragged(float deltaX) {
+        // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
+        // is continually called.
+        if (mFlyout.getWidth() <= 0) {
+            return;
+        }
+
         final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
         mFlyoutDragDeltaX = deltaX;
 
@@ -1062,7 +1132,7 @@
                 onLeft ? -deltaX / mFlyout.getWidth() : deltaX / mFlyout.getWidth();
         mFlyout.setCollapsePercent(Math.min(1f, Math.max(0f, collapsePercent)));
 
-        // Calculate how to translate the flyout if it has been dragged too far in etiher direction.
+        // Calculate how to translate the flyout if it has been dragged too far in either direction.
         float overscrollTranslation = 0f;
         if (collapsePercent < 0f || collapsePercent > 1f) {
             // Whether we are more than 100% transitioned to the dot.
@@ -1073,7 +1143,6 @@
             // after it has already become the dot.
             final boolean overscrollingLeft =
                     (onLeft && collapsePercent > 1f) || (!onLeft && collapsePercent < 0f);
-
             overscrollTranslation =
                     (overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
                             * (overscrollingLeft ? -1 : 1)
@@ -1086,6 +1155,19 @@
     }
 
     /**
+     * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+     * once it collapses.
+     */
+    @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+
+    void onFlyoutTapped() {
+        mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+
+        mFlyout.removeCallbacks(mHideFlyout);
+        mHideFlyout.run();
+    }
+
+    /**
      * Called when the flyout drag has finished, and returns true if the gesture successfully
      * dismissed the flyout.
      */
@@ -1181,9 +1263,6 @@
 
                 animateDesaturateAndDarken(magnetView, true);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(true);
-
         } else {
             mAnimatingMagnet = false;
 
@@ -1194,8 +1273,6 @@
                 mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
                 animateDesaturateAndDarken(magnetView, false);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(false);
         }
 
         mVibrator.vibrate(VibrationEffect.get(toTarget
@@ -1214,7 +1291,7 @@
             mAfterMagnet = null;
 
             mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-            mDismissContainer.animateEncirclingCircleDisappearance();
+            mDismissContainer.springOut();
 
             // 'Implode' the stack and then hide the dismiss target.
             if (touchedView == this) {
@@ -1252,7 +1329,7 @@
         }
     }
 
-    /** Animates in the dismiss target, including the gradient behind it. */
+    /** Animates in the dismiss target. */
     private void springInDismissTarget() {
         if (mShowingDismiss) {
             return;
@@ -1270,7 +1347,7 @@
      * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
      * were dragged into the target and encircled.
      */
-    private void springOutDismissTargetAndHideCircle() {
+    private void hideDismissTarget() {
         if (!mShowingDismiss) {
             return;
         }
@@ -1287,6 +1364,12 @@
     /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
     private void animateFlyoutCollapsed(boolean collapsed, float velX) {
         final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        // If the flyout was tapped, we want a higher stiffness for the collapse animation so it's
+        // faster.
+        mFlyoutTransitionSpring.getSpring().setStiffness(
+                (mBubbleToExpandAfterFlyoutCollapse != null)
+                        ? SpringForce.STIFFNESS_MEDIUM
+                        : SpringForce.STIFFNESS_LOW);
         mFlyoutTransitionSpring
                 .setStartValue(mFlyoutDragDeltaX)
                 .setStartVelocity(velX)
@@ -1295,141 +1378,140 @@
                         : 0f);
     }
 
-    /**
-     * Calculates how large the expanded view of the bubble can be. This takes into account the
-     * y position when the bubbles are expanded as well as the bounds of the dismiss target.
-     */
-    int getMaxExpandedHeight() {
-        int expandedY = (int) mExpandedAnimationController.getExpandedY();
-        // PIP dismiss view uses FLAG_LAYOUT_IN_SCREEN so we need to subtract the bottom inset
-        int pipDismissHeight = mPipDismissHeight - getBottomInset();
-        return mDisplaySize.y - expandedY - mBubbleSize - pipDismissHeight;
+    /** Updates the dot visibility, this is used in response to a zen mode config change. */
+    void updateDots() {
+        int bubbsCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbsCount; i++) {
+            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            // If nothing changed the animation won't happen
+            bv.updateDotVisibility(true /* animate */);
+        }
     }
 
     /**
      * Calculates the y position of the expanded view when it is expanded.
      */
-    float getYPositionForExpandedView() {
-        return getStatusBarHeight() + mBubbleSize + mBubblePadding + mPointerHeight;
+    float getExpandedViewY() {
+        return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop + mPointerHeight;
     }
 
     /**
-     * Called when the height of the currently expanded view has changed (not via an
-     * update to the bubble's desired height but for some other reason, e.g. permission view
-     * goes away).
-     */
-    void onExpandedHeightChanged() {
-        if (mIsExpanded) {
-            requestUpdate();
-        }
-    }
-
-    /** Sets whether all bubbles in the stack should not show the 'new' dot. */
-    void setSuppressNewDot(boolean suppressNewDot) {
-        mSuppressNewDot = suppressNewDot;
-
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            bv.setSuppressDot(suppressNewDot, true /* animate */);
-        }
-    }
-
-    /**
-     * Sets whether the flyout should not appear, even if the notif otherwise would generate one.
-     */
-    void setSuppressFlyout(boolean suppressFlyout) {
-        mSuppressFlyout = suppressFlyout;
-    }
-
-    /**
-     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
-     * previous one animates out.
-     */
-    private Runnable mAfterFlyoutHides;
-
-    /**
      * Animates in the flyout for the given bubble, if available, and then hides it after some time.
      */
     @VisibleForTesting
     void animateInFlyoutForBubble(Bubble bubble) {
-        final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
-
-        // Show the message if one exists, and we're not expanded or animating expansion.
-        if (updateMessage != null
-                && !isExpanded()
-                && !mIsExpansionAnimating
-                && !mIsGestureInProgress
-                && !mSuppressFlyout) {
-            if (bubble.iconView != null) {
-                // Temporarily suppress the dot while the flyout is visible.
-                bubble.iconView.setSuppressDot(
-                        true /* suppressDot */, false /* animate */);
-
-                mFlyoutDragDeltaX = 0f;
-                mFlyout.setAlpha(0f);
-
-                if (mAfterFlyoutHides != null) {
-                    mAfterFlyoutHides.run();
-                }
-
-                mAfterFlyoutHides = () -> {
-                    if (bubble.iconView == null) {
-                        return;
-                    }
-
-                    // If we're going to suppress the dot, make it visible first so it'll
-                    // visibly animate away.
-                    if (mSuppressNewDot) {
-                        bubble.iconView.setSuppressDot(
-                                false /* suppressDot */, false /* animate */);
-                    }
-
-                    // Reset dot suppression. If we're not suppressing due to DND, then
-                    // stop suppressing it with no animation (since the flyout has
-                    // transformed into the dot). If we are suppressing due to DND, animate
-                    // it away.
-                    bubble.iconView.setSuppressDot(
-                            mSuppressNewDot /* suppressDot */,
-                            mSuppressNewDot /* animate */);
-                };
-
-                // Post in case layout isn't complete and getWidth returns 0.
-                post(() -> {
-                    // An auto-expanding bubble could have been posted during the time it takes to
-                    // layout.
-                    if (isExpanded()) {
-                        return;
-                    }
-
-                    mFlyout.showFlyout(
-                            updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
-                            mStackAnimationController.isStackOnLeftSide(),
-                            bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
-                });
-            }
-
-            mFlyout.removeCallbacks(mHideFlyout);
-            mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
+        if (!bubble.showFlyoutForBubble()) {
+            // In case flyout was suppressed for this update, reset now.
+            bubble.setSuppressFlyout(false);
+            return;
         }
+        if (updateMessage == null
+                || isExpanded()
+                || mIsExpansionAnimating
+                || mIsGestureInProgress
+                || mBubbleToExpandAfterFlyoutCollapse != null
+                || bubble.getIconView() == null) {
+            // Skip the message if none exists, we're expanded or animating expansion, or we're
+            // about to expand a bubble from the previous tapped flyout, or if bubble view is null.
+            return;
+        }
+        mFlyoutDragDeltaX = 0f;
+        clearFlyoutOnHide();
+        mFlyoutOnHide = () -> {
+            resetDot(bubble);
+            if (mBubbleToExpandAfterFlyoutCollapse == null) {
+                return;
+            }
+            mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
+            mBubbleData.setExpanded(true);
+            mBubbleToExpandAfterFlyoutCollapse = null;
+        };
+        mFlyout.setVisibility(INVISIBLE);
+
+        // Temporarily suppress the dot while the flyout is visible.
+        bubble.getIconView().setSuppressDot(
+                true /* suppressDot */, false /* animate */);
+
+        // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
+        post(() -> {
+            // An auto-expanding bubble could have been posted during the time it takes to
+            // layout.
+            if (isExpanded()) {
+                return;
+            }
+            final Runnable expandFlyoutAfterDelay = () -> {
+                mAnimateInFlyout = () -> {
+                    mFlyout.setVisibility(VISIBLE);
+                    mFlyoutDragDeltaX =
+                            mStackAnimationController.isStackOnLeftSide()
+                                    ? -mFlyout.getWidth()
+                                    : mFlyout.getWidth();
+                    animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
+                    mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+                };
+                mFlyout.postDelayed(mAnimateInFlyout, 200);
+            };
+            mFlyout.setupFlyoutStartingAsDot(
+                    updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+                    mStackAnimationController.isStackOnLeftSide(),
+                    bubble.getIconView().getBadgeColor() /* dotColor */,
+                    expandFlyoutAfterDelay /* onLayoutComplete */,
+                    mFlyoutOnHide,
+                    bubble.getIconView().getDotCenter());
+            mFlyout.bringToFront();
+        });
+        mFlyout.removeCallbacks(mHideFlyout);
+        mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+    }
+
+    private void resetDot(Bubble bubble) {
+        final boolean suppressDot = !bubble.showBubbleDot();
+        // If we're going to suppress the dot, make it visible first so it'll
+        // visibly animate away.
+
+        if (suppressDot) {
+            bubble.getIconView().setSuppressDot(
+                    false /* suppressDot */, false /* animate */);
+        }
+        // Reset dot suppression. If we're not suppressing due to DND, then
+        // stop suppressing it with no animation (since the flyout has
+        // transformed into the dot). If we are suppressing due to DND, animate
+        // it away.
+        bubble.getIconView().setSuppressDot(
+                suppressDot /* suppressDot */,
+                suppressDot /* animate */);
     }
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
     private void hideFlyoutImmediate() {
-        if (mAfterFlyoutHides != null) {
-            mAfterFlyoutHides.run();
-        }
-
+        clearFlyoutOnHide();
+        mFlyout.removeCallbacks(mAnimateInFlyout);
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.hideFlyout();
     }
 
+    private void clearFlyoutOnHide() {
+        mFlyout.removeCallbacks(mAnimateInFlyout);
+        if (mFlyoutOnHide == null) {
+            return;
+        }
+        mFlyoutOnHide.run();
+        mFlyoutOnHide = null;
+    }
+
     @Override
     public void getBoundsOnScreen(Rect outRect) {
         if (!mIsExpanded) {
             if (mBubbleContainer.getChildCount() > 0) {
                 mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
             }
+            // Increase the touch target size of the bubble
+            outRect.top -= mBubbleTouchPadding;
+            outRect.left -= mBubbleTouchPadding;
+            outRect.right += mBubbleTouchPadding;
+            outRect.bottom += mBubbleTouchPadding;
         } else {
             mBubbleContainer.getBoundsOnScreen(outRect);
         }
@@ -1454,14 +1536,6 @@
         return 0;
     }
 
-    private int getBottomInset() {
-        if (getRootWindowInsets() != null) {
-            WindowInsets insets = getRootWindowInsets();
-            return insets.getSystemWindowInsetBottom();
-        }
-        return 0;
-    }
-
     private boolean isIntersecting(View view, float x, float y) {
         mTempLoc = view.getLocationOnScreen();
         mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
@@ -1479,33 +1553,33 @@
     }
 
     private void updateExpandedBubble() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
         }
         mExpandedViewContainer.removeAllViews();
         if (mExpandedBubble != null && mIsExpanded) {
-            mExpandedViewContainer.addView(mExpandedBubble.expandedView);
-            mExpandedBubble.expandedView.populateExpandedView();
+            mExpandedViewContainer.addView(mExpandedBubble.getExpandedView());
+            mExpandedBubble.getExpandedView().populateExpandedView();
             mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
             mExpandedViewContainer.setAlpha(1.0f);
         }
     }
 
-    private void applyCurrentState() {
-        if (DEBUG) {
-            Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+    private void updateExpandedView() {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
+            Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
         }
 
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (mIsExpanded) {
             // First update the view so that it calculates a new height (ensuring the y position
             // calculation is correct)
-            mExpandedBubble.expandedView.updateView();
-            final float y = getYPositionForExpandedView();
+            mExpandedBubble.getExpandedView().updateView();
+            final float y = getExpandedViewY();
             if (!mExpandedViewYAnim.isRunning()) {
                 // We're not animating so set the value
                 mExpandedViewContainer.setTranslationY(y);
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             } else {
                 // We are animating so update the value; there is an end listener on the animator
                 // that will ensure expandedeView.updateView gets called.
@@ -1514,29 +1588,17 @@
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
-        updateBubbleShadowsAndDotPosition(false);
+        updateBubbleZOrdersAndDotPosition(false);
     }
 
     /** Sets the appropriate Z-order and dot position for each bubble in the stack. */
-    private void updateBubbleShadowsAndDotPosition(boolean animate) {
-        int bubbsCount = mBubbleContainer.getChildCount();
-        for (int i = 0; i < bubbsCount; i++) {
+    private void updateBubbleZOrdersAndDotPosition(boolean animate) {
+        int bubbleCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility(true /* animate */);
             bv.setZ((BubbleController.MAX_BUBBLES
                     * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
-
-            // Draw the shadow around the circle inscribed within the bubble's bounds. This
-            // (intentionally) does not draw a shadow behind the update dot, which should be drawing
-            // its own shadow since it's on a different (higher) plane.
-            bv.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setOval(0, 0, mBubbleSize, mBubbleSize);
-                }
-            });
-            bv.setClipToOutline(false);
-
             // If the dot is on the left, and so is the stack, we need to change the dot position.
             if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
                 bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
@@ -1545,7 +1607,7 @@
     }
 
     private void updatePointerPosition() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updatePointerPosition()");
         }
 
@@ -1557,13 +1619,11 @@
         int index = getBubbleIndex(expandedBubble);
         float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
         float halfBubble = mBubbleSize / 2f;
+        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble;
+        // Padding might be adjusted for insets, so get it directly from the view
+        bubbleCenter -= mExpandedViewContainer.getPaddingLeft();
 
-        // Bubbles live in expanded view container (x includes expanded view padding).
-        // Pointer lives in expanded view, which has padding (x does not include padding).
-        // Remove padding when deriving pointer location from bubbles.
-        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
-
-        expandedBubble.expandedView.setPointerPosition(bubbleCenter);
+        expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
     }
 
     /**
@@ -1584,7 +1644,7 @@
         if (bubble == null) {
             return 0;
         }
-        return mBubbleContainer.indexOfChild(bubble.iconView);
+        return mBubbleContainer.indexOfChild(bubble.getIconView());
     }
 
     /**
@@ -1617,8 +1677,8 @@
      * @param action the user interaction enum.
      */
     private void logBubbleEvent(@Nullable Bubble bubble, int action) {
-        if (bubble == null || bubble.entry == null
-                || bubble.entry.notification == null) {
+        if (bubble == null || bubble.getEntry() == null
+                || bubble.getEntry().notification == null) {
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     null /* package name */,
                     null /* notification channel */,
@@ -1630,9 +1690,9 @@
                     getNormalizedYPosition(),
                     false /* unread bubble */,
                     false /* on-going bubble */,
-                    false /* foreground bubble */);
+                    false /* isAppForeground (unused) */);
         } else {
-            StatusBarNotification notification = bubble.entry.notification;
+            StatusBarNotification notification = bubble.getEntry().notification;
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     notification.getPackageName(),
                     notification.getNotification().getChannelId(),
@@ -1642,9 +1702,9 @@
                     action,
                     getNormalizedXPosition(),
                     getNormalizedYPosition(),
-                    bubble.entry.showInShadeWhenBubble(),
-                    bubble.entry.isForegroundService(),
-                    BubbleController.isForegroundApp(mContext, notification.getPackageName()));
+                    bubble.showInShadeWhenBubble(),
+                    bubble.isOngoing(),
+                    false /* isAppForeground (unused) */);
         }
     }
 
@@ -1656,7 +1716,7 @@
         if (!isExpanded()) {
             return false;
         }
-        return mExpandedBubble.expandedView.performBackPressIfNeeded();
+        return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
     }
 
     /** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 8fe8bd3..4240e06 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -45,7 +44,6 @@
      */
     private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
 
-    private static final String TAG = "BubbleTouchHandler";
     /**
      * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
      * towards the center of the screen (where the dismiss target is). This value is the width of
@@ -66,11 +64,10 @@
     private int mTouchSlopSquared;
     private VelocityTracker mVelocityTracker;
 
-    private boolean mInDismissTarget;
-    private Handler mHandler = new Handler();
-
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
+    /** Whether the current touched view is in the dismiss target. */
+    private boolean mInDismissTarget;
 
     BubbleTouchHandler(BubbleStackView stackView,
             BubbleData bubbleData, Context context) {
@@ -98,6 +95,15 @@
             return false;
         }
 
+        if (!(mTouchedView instanceof BubbleView)
+                && !(mTouchedView instanceof BubbleStackView)
+                && !(mTouchedView instanceof BubbleFlyoutView)) {
+            // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
+            // of expanded view).
+            resetForNextGesture();
+            return false;
+        }
+
         final boolean isStack = mStack.equals(mTouchedView);
         final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
         final float rawX = event.getRawX();
@@ -193,9 +199,8 @@
                                 }
                             });
                 } else if (isFlyout) {
-                    // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
                     if (!mBubbleData.isExpanded() && !mMovedEnough) {
-                        mBubbleData.setExpanded(true);
+                        mStack.onFlyoutTapped();
                     }
                 } else if (mMovedEnough) {
                     if (isStack) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6f1ed28..4512aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -19,16 +19,23 @@
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
+import android.util.PathParser;
 import android.widget.FrameLayout;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.ShadowGenerator;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,23 +45,25 @@
  * A floating object on the screen that can post message updates.
  */
 public class BubbleView extends FrameLayout {
-    private static final String TAG = "BubbleView";
 
     private static final int DARK_ICON_ALPHA = 180;
     private static final double ICON_MIN_CONTRAST = 4.1;
-    private static final int DEFAULT_BACKGROUND_COLOR =  Color.LTGRAY;
+    private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
     // Same value as Launcher3 badge code
     private static final float WHITE_SCRIM_ALPHA = 0.54f;
     private Context mContext;
 
     private BadgedImageView mBadgedImageView;
     private int mBadgeColor;
-    private int mPadding;
     private int mIconInset;
+    private Drawable mUserBadgedAppIcon;
 
-    private boolean mSuppressDot = false;
+    // mBubbleIconFactory cannot be static because it depends on Context.
+    private BubbleIconFactory mBubbleIconFactory;
 
-    private NotificationEntry mEntry;
+    private boolean mSuppressDot;
+
+    private Bubble mBubble;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -71,8 +80,6 @@
     public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mContext = context;
-        // XXX: can this padding just be on the view and we look it up?
-        mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
         mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
     }
 
@@ -88,16 +95,15 @@
     }
 
     /**
-     * Populates this view with a notification.
+     * Populates this view with a bubble.
      * <p>
-     * This should only be called when a new notification is being set on the view, updates to the
-     * current notification should use {@link #update(NotificationEntry)}.
+     * This should only be called when a new bubble is being set on the view, updates to the
+     * current bubble should use {@link #update(Bubble)}.
      *
-     * @param entry the notification to display as a bubble.
+     * @param bubble the bubble to display in this view.
      */
-    public void setNotif(NotificationEntry entry) {
-        mEntry = entry;
-        updateViews();
+    public void setBubble(Bubble bubble) {
+        mBubble = bubble;
     }
 
     /**
@@ -105,7 +111,7 @@
      */
     @Nullable
     public NotificationEntry getEntry() {
-        return mEntry;
+        return mBubble != null ? mBubble.getEntry() : null;
     }
 
     /**
@@ -113,24 +119,35 @@
      */
     @Nullable
     public String getKey() {
-        return (mEntry != null) ? mEntry.key : null;
+        return (mBubble != null) ? mBubble.getKey() : null;
     }
 
     /**
-     * Updates the UI based on the entry, updates badge and animates messages as needed.
+     * Updates the UI based on the bubble, updates badge and animates messages as needed.
      */
-    public void update(NotificationEntry entry) {
-        mEntry = entry;
+    public void update(Bubble bubble) {
+        mBubble = bubble;
         updateViews();
     }
 
     /**
+     * @param factory Factory for creating normalized bubble icons.
+     */
+    public void setBubbleIconFactory(BubbleIconFactory factory) {
+        mBubbleIconFactory = factory;
+    }
+
+    public void setAppIcon(Drawable appIcon) {
+        mUserBadgedAppIcon = appIcon;
+    }
+
+    /**
      * @return the {@link ExpandableNotificationRow} view to display notification content when the
      * bubble is expanded.
      */
     @Nullable
     public ExpandableNotificationRow getRowView() {
-        return (mEntry != null) ? mEntry.getRow() : null;
+        return (mBubble != null) ? mBubble.getEntry().getRow() : null;
     }
 
     /** Changes the dot's visibility to match the bubble view's state. */
@@ -138,7 +155,6 @@
         updateDotVisibility(animate, null /* after */);
     }
 
-
     /**
      * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
      * flyout is visible or animating, to hide the dot until the flyout visually transforms into it.
@@ -150,18 +166,23 @@
 
     /** Sets the position of the 'new' dot, animating it out and back in if requested. */
     void setDotPosition(boolean onLeft, boolean animate) {
-        if (animate && onLeft != mBadgedImageView.getDotPosition() && !mSuppressDot) {
+        if (animate && onLeft != mBadgedImageView.getDotOnLeft() && shouldShowDot()) {
             animateDot(false /* showDot */, () -> {
-                mBadgedImageView.setDotPosition(onLeft);
+                mBadgedImageView.setDotOnLeft(onLeft);
                 animateDot(true /* showDot */, null);
             });
         } else {
-            mBadgedImageView.setDotPosition(onLeft);
+            mBadgedImageView.setDotOnLeft(onLeft);
         }
     }
 
+    float[] getDotCenter() {
+        float[] unscaled = mBadgedImageView.getDotCenter();
+        return new float[]{unscaled[0], unscaled[1]};
+    }
+
     boolean getDotPositionOnLeft() {
-        return mBadgedImageView.getDotPosition();
+        return mBadgedImageView.getDotOnLeft();
     }
 
     /**
@@ -169,12 +190,12 @@
      * after animation if requested.
      */
     private void updateDotVisibility(boolean animate, Runnable after) {
-        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
-
+        final boolean showDot = shouldShowDot();
         if (animate) {
             animateDot(showDot, after);
         } else {
             mBadgedImageView.setShowDot(showDot);
+            mBadgedImageView.setDotScale(showDot ? 1f : 0f);
         }
     }
 
@@ -182,62 +203,86 @@
      * Animates the badge to show or hide.
      */
     private void animateDot(boolean showDot, Runnable after) {
-        if (mBadgedImageView.isShowingDot() != showDot) {
-            if (showDot) {
-                mBadgedImageView.setShowDot(true);
-            }
-
-            mBadgedImageView.clearAnimation();
-            mBadgedImageView.animate().setDuration(200)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .setUpdateListener((valueAnimator) -> {
-                        float fraction = valueAnimator.getAnimatedFraction();
-                        fraction = showDot ? fraction : 1f - fraction;
-                        mBadgedImageView.setDotScale(fraction);
-                    }).withEndAction(() -> {
-                        if (!showDot) {
-                            mBadgedImageView.setShowDot(false);
-                        }
-
-                        if (after != null) {
-                            after.run();
-                        }
-            }).start();
+        if (mBadgedImageView.isShowingDot() == showDot) {
+            return;
         }
+        // Do NOT wait until after animation ends to setShowDot
+        // to avoid overriding more recent showDot states.
+        mBadgedImageView.setShowDot(showDot);
+        mBadgedImageView.clearAnimation();
+        mBadgedImageView.animate().setDuration(200)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setUpdateListener((valueAnimator) -> {
+                    float fraction = valueAnimator.getAnimatedFraction();
+                    fraction = showDot ? fraction : 1f - fraction;
+                    mBadgedImageView.setDotScale(fraction);
+                }).withEndAction(() -> {
+            mBadgedImageView.setDotScale(showDot ? 1f : 0f);
+            if (after != null) {
+                after.run();
+            }
+        }).start();
     }
 
     void updateViews() {
-        if (mEntry == null) {
+        if (mBubble == null || mBubbleIconFactory == null) {
             return;
         }
-        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
-        Notification n = mEntry.notification.getNotification();
-        Icon ic;
-        boolean needsTint;
-        if (metadata != null) {
-            ic = metadata.getIcon();
-            needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
-        } else {
-            needsTint = n.getLargeIcon() == null;
-            ic = needsTint ? n.getSmallIcon() : n.getLargeIcon();
-        }
+        // Update icon.
+        Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
+        Notification n = mBubble.getEntry().notification.getNotification();
+        Icon ic = metadata.getIcon();
+        boolean needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
+
         Drawable iconDrawable = ic.loadDrawable(mContext);
         if (needsTint) {
-            mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
-        } else {
-            mBadgedImageView.setImageDrawable(iconDrawable);
+            iconDrawable = buildIconWithTint(iconDrawable, n.color);
         }
+        Bitmap bubbleIcon = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
+                null /* user */,
+                true /* shrinkNonAdaptiveIcons */).icon;
+
+        // Give it a shadow
+        Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(mUserBadgedAppIcon,
+                1f, mBubbleIconFactory.getBadgeSize());
+        Canvas c = new Canvas();
+        ShadowGenerator shadowGenerator = new ShadowGenerator(mBubbleIconFactory.getBadgeSize());
+        c.setBitmap(userBadgedBitmap);
+        shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+
+        mBubbleIconFactory.badgeWithDrawable(bubbleIcon,
+                new BitmapDrawable(mContext.getResources(), userBadgedBitmap));
+        mBadgedImageView.setImageBitmap(bubbleIcon);
+
+        // Update badge.
         int badgeColor = determineDominateColor(iconDrawable, n.color);
         mBadgeColor = badgeColor;
         mBadgedImageView.setDotColor(badgeColor);
-        animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
+
+        // Update dot.
+        Path iconPath = PathParser.createPathFromPathData(
+                getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = mBubbleIconFactory.getNormalizer().getScale(iconDrawable,
+                null /* outBounds */, null /* path */, null /* outMaskShape */);
+        float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        mBadgedImageView.drawDot(iconPath);
+
+        animateDot(shouldShowDot(), null /* after */);
+    }
+
+    boolean shouldShowDot() {
+        return mBubble.showBubbleDot() && !mSuppressDot;
     }
 
     int getBadgeColor() {
         return mBadgeColor;
     }
 
-    private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
+    private AdaptiveIconDrawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
         iconDrawable = checkTint(iconDrawable, backgroundColor);
         InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
         ColorDrawable background = new ColorDrawable(backgroundColor);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1fa0e12..59d68bc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.view.DisplayCutout;
 import android.view.View;
 import android.view.WindowInsets;
 
@@ -26,10 +29,13 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Set;
 
 /**
@@ -46,24 +52,29 @@
      */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
-    /** How much to scale down bubbles when they're animating in/out. */
-    private static final float ANIMATE_SCALE_PERCENT = 0.5f;
+    /** Duration of the expand/collapse target path animation. */
+    private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
 
-    /** The stack position to collapse back to in {@link #collapseBackToStack}. */
-    private PointF mCollapseToPoint;
+    /** Stiffness for the expand/collapse path-following animation. */
+    private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
+
+    /** What percentage of the screen to use when centering the bubbles in landscape. */
+    private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
 
     /** Horizontal offset between bubbles, which we need to know to re-stack them. */
     private float mStackOffsetPx;
-    /** Spacing between bubbles in the expanded state. */
-    private float mBubblePaddingPx;
+    /** Space between status bar and bubbles in the expanded state. */
+    private float mBubblePaddingTop;
     /** Size of each bubble. */
     private float mBubbleSizePx;
     /** Height of the status bar. */
     private float mStatusBarHeight;
     /** Size of display. */
     private Point mDisplaySize;
-    /** Size of dismiss target at bottom of screen. */
-    private float mPipDismissHeight;
+    /** Max number of bubbles shown in row above expanded view.*/
+    private int mBubblesMaxRendered;
+    /** What the current screen orientation is. */
+    private int mScreenOrientation;
 
     /** Whether the dragged-out bubble is in the dismiss target. */
     private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -86,10 +97,13 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
+    private float mLauncherGridDiff;
 
-    public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
-        mDisplaySize = displaySize;
+    public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
+            int orientation) {
+        updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
+        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -109,7 +123,7 @@
         mAnimatingExpand = true;
         mAfterExpand = after;
 
-        startOrUpdateExpandAnimation();
+        startOrUpdatePathAnimation(true /* expanding */);
     }
 
     /** Animate collapsing the bubbles back to their stacked position. */
@@ -119,43 +133,105 @@
         mAfterCollapse = after;
         mCollapsePoint = collapsePoint;
 
-        startOrUpdateCollapseAnimation();
+        startOrUpdatePathAnimation(false /* expanding */);
     }
 
-    private void startOrUpdateExpandAnimation() {
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
-                .startAll(() -> {
-                    mAnimatingExpand = false;
-
-                    if (mAfterExpand != null) {
-                        mAfterExpand.run();
-                    }
-
-                    mAfterExpand = null;
-                });
+    /**
+     * Update effective screen width based on current orientation.
+     * @param orientation Landscape or portrait.
+     * @param displaySize Updated display size.
+     */
+    public void updateOrientation(int orientation, Point displaySize) {
+        mScreenOrientation = orientation;
+        mDisplaySize = displaySize;
+        if (mLayout != null) {
+            Resources res = mLayout.getContext().getResources();
+            mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            mStatusBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height);
+        }
     }
 
-    private void startOrUpdateCollapseAnimation() {
-        // Stack to the left if we're going to the left, or right if not.
-        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> {
-                    animation.position(
-                            mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
-                            mCollapsePoint.y);
-                })
-                .startAll(() -> {
-                    mAnimatingCollapse = false;
+    /**
+     * Animates the bubbles along a curved path, either to expand them along the top or collapse
+     * them back into a stack.
+     */
+    private void startOrUpdatePathAnimation(boolean expanding) {
+        Runnable after;
 
-                    if (mAfterCollapse != null) {
-                        mAfterCollapse.run();
-                    }
+        if (expanding) {
+            after = () -> {
+                mAnimatingExpand = false;
 
-                    mAfterCollapse = null;
-                });
+                if (mAfterExpand != null) {
+                    mAfterExpand.run();
+                }
+
+                mAfterExpand = null;
+            };
+        } else {
+            after = () -> {
+                mAnimatingCollapse = false;
+
+                if (mAfterCollapse != null) {
+                    mAfterCollapse.run();
+                }
+
+                mAfterCollapse = null;
+            };
+        }
+
+        // Animate each bubble individually, since each path will end in a different spot.
+        animationsForChildrenFromIndex(0, (index, animation) -> {
+            final View bubble = mLayout.getChildAt(index);
+
+            // Start a path at the bubble's current position.
+            final Path path = new Path();
+            path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
+
+            final float expandedY = getExpandedY();
+            if (expanding) {
+                // If we're expanding, first draw a line from the bubble's current position to the
+                // top of the screen.
+                path.lineTo(bubble.getTranslationX(), expandedY);
+
+                // Then, draw a line across the screen to the bubble's resting position.
+                path.lineTo(getBubbleLeft(index), expandedY);
+            } else {
+                final float sideMultiplier =
+                        mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
+                final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+
+                // If we're collapsing, draw a line from the bubble's current position to the side
+                // of the screen where the bubble will be stacked.
+                path.lineTo(stackedX, expandedY);
+
+                // Then, draw a line down to the stack position.
+                path.lineTo(stackedX, mCollapsePoint.y);
+            }
+
+            // The lead bubble should be the bubble with the longest distance to travel when we're
+            // expanding, and the bubble with the shortest distance to travel when we're collapsing.
+            // During expansion from the left side, the last bubble has to travel to the far right
+            // side, so we have it lead and 'pull' the rest of the bubbles into place. From the
+            // right side, the first bubble is traveling to the top left, so it leads. During
+            // collapse to the left, the first bubble has the shortest travel time back to the stack
+            // position, so it leads (and vice versa).
+            final boolean firstBubbleLeads =
+                    (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+                            || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+            final int startDelay = firstBubbleLeads
+                    ? (index * 10)
+                    : ((mLayout.getChildCount() - index) * 10);
+
+            animation
+                    .followAnimatedTargetAlongPath(
+                            path,
+                            EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
+                            Interpolators.LINEAR /* targetAnimInterpolator */)
+                    .withStartDelay(startDelay)
+                    .withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
+        }).startAll(after);
     }
 
     /** Prepares the given bubble to be dragged out. */
@@ -265,6 +341,7 @@
     public void onGestureFinished() {
         mBubbleDraggedOutEnough = false;
         mBubbleDraggingOut = null;
+        updateBubblePositions();
     }
 
     /**
@@ -276,41 +353,38 @@
                 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
     }
 
-    /**
-     * Animates the bubbles, starting at the given index, to the left or right by the given number
-     * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
-     * positions.
-     */
-    private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
-        animationsForChildrenFromIndex(
-                startIndex,
-                (index, animation) ->
-                        animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
-            .startAll();
-    }
-
     /** The Y value of the row of expanded bubbles. */
     public float getExpandedY() {
         if (mLayout == null || mLayout.getRootWindowInsets() == null) {
             return 0;
         }
         final WindowInsets insets = mLayout.getRootWindowInsets();
-        return mBubblePaddingPx + Math.max(
+        return mBubblePaddingTop + Math.max(
             mStatusBarHeight,
             insets.getDisplayCutout() != null
                 ? insets.getDisplayCutout().getSafeInsetTop()
                 : 0);
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("ExpandedAnimationController state:");
+        pw.print("  isActive:          "); pw.println(isActiveController());
+        pw.print("  animatingExpand:   "); pw.println(mAnimatingExpand);
+        pw.print("  animatingCollapse: "); pw.println(mAnimatingCollapse);
+        pw.print("  bubbleInDismiss:   "); pw.println(mIndividualBubbleWithinDismissTarget);
+        pw.print("  springingBubble:   "); pw.println(mSpringingBubbleToTouch);
+    }
+
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         final Resources res = layout.getResources();
         mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+        mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
 
         // Ensure that all child views are at 1x scale, and visible, in case they were animating
         // in.
@@ -351,11 +425,11 @@
         // If a bubble is added while the expand/collapse animations are playing, update the
         // animation to include the new bubble.
         if (mAnimatingExpand) {
-            startOrUpdateExpandAnimation();
+            startOrUpdatePathAnimation(true /* expanding */);
         } else if (mAnimatingCollapse) {
-            startOrUpdateCollapseAnimation();
+            startOrUpdatePathAnimation(false /* expanding */);
         } else {
-            child.setTranslationX(getXForChildAtIndex(index));
+            child.setTranslationX(getBubbleLeft(index));
             animationForChild(child)
                     .translationY(
                             getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -389,6 +463,12 @@
     @Override
     void onChildReordered(View child, int oldIndex, int newIndex) {
         updateBubblePositions();
+
+        // We expect reordering during collapse, since we'll put the last selected bubble on top.
+        // Update the collapse animation so they end up in the right stacked positions.
+        if (mAnimatingCollapse) {
+            startOrUpdatePathAnimation(false /* expanding */);
+        }
     }
 
     private void updateBubblePositions() {
@@ -411,35 +491,100 @@
         }
     }
 
-    /** Returns the appropriate X translation value for a bubble at the given index. */
-    private float getXForChildAtIndex(int index) {
-        return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
-    }
-
     /**
      * @param index Bubble index in row.
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleFromRowLeft;
+    }
+
+    /**
+     * When expanded, the bubbles are centered in the screen. In portrait, all available space is
+     * used. In landscape we have too much space so the value is restricted. This method accounts
+     * for window decorations (nav bar, cutouts).
+     *
+     * @return the desired width to display the expanded bubbles in.
+     */
+    private float getWidthForDisplayingBubbles() {
+        final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */);
+        if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            // display size y in landscape will be the smaller dimension of the screen
+            return Math.max(mDisplaySize.y, availableWidth * CENTER_BUBBLES_LANDSCAPE_PERCENT);
+        } else {
+            return availableWidth;
+        }
+    }
+
+    /**
+     * Determines the available screen width without the cutout.
+     *
+     * @param subtractStableInsets Whether or not stable insets should also be removed from the
+     *                            returned width.
+     * @return the total screen width available accounting for cutouts and insets,
+     * iff {@param includeStableInsets} is true.
+     */
+    private float getAvailableScreenWidth(boolean subtractStableInsets) {
+        float availableSize = mDisplaySize.x;
+        WindowInsets insets = mLayout != null ? mLayout.getRootWindowInsets() : null;
+        if (insets != null) {
+            int cutoutLeft = 0;
+            int cutoutRight = 0;
+            DisplayCutout cutout = insets.getDisplayCutout();
+            if (cutout != null) {
+                cutoutLeft = cutout.getSafeInsetLeft();
+                cutoutRight = cutout.getSafeInsetRight();
+            }
+            final int stableLeft = subtractStableInsets ? insets.getStableInsetLeft() : 0;
+            final int stableRight = subtractStableInsets ? insets.getStableInsetRight() : 0;
+            availableSize -= Math.max(stableLeft, cutoutLeft);
+            availableSize -= Math.max(stableRight, cutoutRight);
+        }
+        return availableSize;
     }
 
     private float getRowLeft() {
         if (mLayout == null) {
             return 0;
         }
+
         int bubbleCount = mLayout.getChildCount();
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSizePx;
-        float gap = (bubbleCount - 1) * mBubblePaddingPx;
-        float row = gap + (float) bubble;
+        final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplaySize.x / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        // This display size we're using includes the size of the insets, we want the true
+        // center of the display minus the notch here, which means we should include the
+        // stable insets (e.g. status bar, nav bar) in this calculation.
+        final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = trueCenter - halfRow;
 
-        return rowLeftFromScreenLeft;
+        return rowLeft;
+    }
+
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        /**
+         * Ordered left to right:
+         *  Screen edge
+         *      [mExpandedViewPadding]
+         *  Expanded view edge
+         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
+         *  Launcher's app icon grid edge that we must match
+         */
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
+
+        final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mBubblesMaxRendered - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 3a33392..563a0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -160,7 +167,7 @@
 
         /** Whether this controller is the currently active controller for its associated layout. */
         protected boolean isActiveController() {
-            return this == mLayout.mController;
+            return mLayout != null && this == mLayout.mController;
         }
 
         protected void setLayout(PhysicsAnimationLayout layout) {
@@ -232,7 +239,7 @@
                 }
 
                 if (endActions != null) {
-                    mLayout.setEndActionForMultipleProperties(
+                    setEndActionForMultipleProperties(
                             runAllEndActions,
                             allAnimatedProperties.toArray(
                                     new DynamicAnimation.ViewProperty[0]));
@@ -243,6 +250,44 @@
                 }
             };
         }
+
+        /**
+         * Sets an end action that will be run when all child animations for a given property have
+         * stopped running.
+         */
+        protected void setEndActionForProperty(
+                Runnable action, DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.put(property, action);
+        }
+
+        /**
+         * Sets an end action that will be run when all child animations for all of the given
+         * properties have stopped running.
+         */
+        protected void setEndActionForMultipleProperties(
+                Runnable action, DynamicAnimation.ViewProperty... properties) {
+            final Runnable checkIfAllFinished = () -> {
+                if (!mLayout.arePropertiesAnimating(properties)) {
+                    action.run();
+
+                    for (DynamicAnimation.ViewProperty property : properties) {
+                        removeEndActionForProperty(property);
+                    }
+                }
+            };
+
+            for (DynamicAnimation.ViewProperty property : properties) {
+                setEndActionForProperty(checkIfAllFinished, property);
+            }
+        }
+
+        /**
+         * Removes the end listener that would have been called when all child animations for a
+         * given property stopped running.
+         */
+        protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.remove(property);
+        }
     }
 
     /**
@@ -275,43 +320,6 @@
         }
     }
 
-    /**
-     * Sets an end action that will be run when all child animations for a given property have
-     * stopped running.
-     */
-    public void setEndActionForProperty(Runnable action, DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.put(property, action);
-    }
-
-    /**
-     * Sets an end action that will be run when all child animations for all of the given properties
-     * have stopped running.
-     */
-    public void setEndActionForMultipleProperties(
-            Runnable action, DynamicAnimation.ViewProperty... properties) {
-        final Runnable checkIfAllFinished = () -> {
-            if (!arePropertiesAnimating(properties)) {
-                action.run();
-
-                for (DynamicAnimation.ViewProperty property : properties) {
-                    removeEndActionForProperty(property);
-                }
-            }
-        };
-
-        for (DynamicAnimation.ViewProperty property : properties) {
-            setEndActionForProperty(checkIfAllFinished, property);
-        }
-    }
-
-    /**
-     * Removes the end listener that would have been called when all child animations for a given
-     * property stopped running.
-     */
-    public void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.remove(property);
-    }
-
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         addViewInternal(child, index, params, false /* isReorder */);
@@ -372,11 +380,22 @@
     /** Checks whether any animations of the given properties are running on the given view. */
     public boolean arePropertiesAnimatingOnView(
             View view, DynamicAnimation.ViewProperty... properties) {
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
         for (DynamicAnimation.ViewProperty property : properties) {
             final SpringAnimation animation = getAnimationFromView(property, view);
             if (animation != null && animation.isRunning()) {
                 return true;
             }
+
+            // If the target animator is running, its update listener will trigger the translation
+            // physics animations at some point. We should consider the translation properties to be
+            // be animating in this case, even if the physics animations haven't been started yet.
+            final boolean isTranslation =
+                    property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y);
+            if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
+                return true;
+            }
         }
 
         return false;
@@ -388,8 +407,18 @@
             return;
         }
 
+        cancelAllAnimationsOfProperties(
+                mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{}));
+    }
+
+    /** Cancels all animations that are running on all child views, for the given properties. */
+    public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) {
+        if (mController == null) {
+            return;
+        }
+
         for (int i = 0; i < getChildCount(); i++) {
-            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+            for (DynamicAnimation.ViewProperty property : properties) {
                 final DynamicAnimation anim = getAnimationAtIndex(property, i);
                 if (anim != null) {
                     anim.cancel();
@@ -400,6 +429,14 @@
 
     /** Cancels all of the physics animations running on the given view. */
     public void cancelAnimationsOnView(View view) {
+        // If present, cancel the target animator so it doesn't restart the translation physics
+        // animations.
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
+        if (targetAnimator != null) {
+            targetAnimator.cancel();
+        }
+
+        // Cancel physics animations on the view.
         for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
             getAnimationFromView(property, view).cancel();
         }
@@ -470,6 +507,11 @@
         return (SpringAnimation) view.getTag(getTagIdForProperty(property));
     }
 
+    /** Retrieves the target animator from the view via the view tag system. */
+    @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
+        return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
+    }
+
     /** Sets up SpringAnimations of the given property for each child view in the layout. */
     private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
         for (int i = 0; i < getChildCount(); i++) {
@@ -587,7 +629,7 @@
          * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
          * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
          */
-        private Runnable[] mPositionEndActions;
+        @Nullable private Runnable[] mPositionEndActions;
 
         /**
          * All of the properties that have been set and will animate when {@link #start} is called.
@@ -603,6 +645,46 @@
         /** The animation controller that last retrieved this animator instance. */
         private PhysicsAnimationController mAssociatedController;
 
+        /**
+         * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
+         * the path is traversed, the view's translation spring animation final positions are
+         * updated such that the view 'follows' the current position on the path.
+         */
+        @Nullable private ObjectAnimator mPathAnimator;
+
+        /** Current position on the path. This is animated by {@link #mPathAnimator}. */
+        private PointF mCurrentPointOnPath = new PointF();
+
+        /**
+         * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
+         * of {@link #mCurrentPointOnPath}.
+         */
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathX") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.x = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.x;
+            }
+        };
+
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathY") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.y = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.y;
+            }
+        };
+
         protected PhysicsPropertyAnimator(View view) {
             this.mView = view;
         }
@@ -628,6 +710,7 @@
 
         /** Animate the view's translationX value to the provided value. */
         public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
         }
 
@@ -640,6 +723,7 @@
 
         /** Animate the view's translationY value to the provided value. */
         public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
         }
 
@@ -661,6 +745,46 @@
             return translationY(translationY);
         }
 
+        /**
+         * Animates a 'target' point that moves along the given path, using the provided duration
+         * and interpolator to animate the target. The view itself is animated using physics-based
+         * animations, whose final positions are updated to the target position as it animates. This
+         * results in the view 'following' the target in a realistic way.
+         *
+         * This method will override earlier calls to {@link #translationX}, {@link #translationY},
+         * or {@link #position}, ultimately animating the view's position to the final point on the
+         * given path.
+         *
+         * Any provided end listeners will be called when the physics-based animations kicked off by
+         * the moving target have completed - not when the target animation completes.
+         */
+        public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
+                Path path,
+                int targetAnimDuration,
+                TimeInterpolator targetAnimInterpolator,
+                Runnable... endActions) {
+            mPathAnimator = ObjectAnimator.ofFloat(
+                    this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
+            mPathAnimator.setDuration(targetAnimDuration);
+            mPathAnimator.setInterpolator(targetAnimInterpolator);
+
+            mPositionEndActions = endActions;
+
+            // Remove translation related values since we're going to ignore them and follow the
+            // path instead.
+            clearTranslationValues();
+            return this;
+        }
+
+        private void clearTranslationValues() {
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
+        }
+
         /** Animate the view's scaleX value to the provided value. */
         public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
             return property(DynamicAnimation.SCALE_X, scaleX, endActions);
@@ -742,7 +866,7 @@
             if (after != null && after.length > 0) {
                 final DynamicAnimation.ViewProperty[] propertiesArray =
                         properties.toArray(new DynamicAnimation.ViewProperty[0]);
-                setEndActionForMultipleProperties(() -> {
+                mAssociatedController.setEndActionForMultipleProperties(() -> {
                     for (Runnable callback : after) {
                         callback.run();
                     }
@@ -774,8 +898,20 @@
                         new Runnable[]{waitForBothXAndY});
             }
 
+            if (mPathAnimator != null) {
+                startPathAnimation();
+            }
+
             // Actually start the animations.
             for (DynamicAnimation.ViewProperty property : properties) {
+                // Don't start translation animations if we're using a path animator, the update
+                // listeners added to that animator will take care of that.
+                if (mPathAnimator != null
+                        && (property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y))) {
+                    return;
+                }
+
                 if (mInitialPropertyValues.containsKey(property)) {
                     property.setValue(mView, mInitialPropertyValues.get(property));
                 }
@@ -797,7 +933,16 @@
 
         /** Returns the set of properties that will animate once {@link #start} is called. */
         protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
-            return mAnimatedProperties.keySet();
+            final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
+                    mAnimatedProperties.keySet());
+
+            // If we're using a path animator, it'll kick off translation animations.
+            if (mPathAnimator != null) {
+                animatedProperties.add(DynamicAnimation.TRANSLATION_X);
+                animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
+            }
+
+            return animatedProperties;
         }
 
         /**
@@ -812,7 +957,7 @@
                 long startDelay,
                 float stiffness,
                 float dampingRatio,
-                Runnable[] afterCallbacks) {
+                Runnable... afterCallbacks) {
             if (view != null) {
                 final SpringAnimation animation =
                         (SpringAnimation) view.getTag(getTagIdForProperty(property));
@@ -855,6 +1000,92 @@
             }
         }
 
+        /**
+         * Updates the final position of a view's animation, without changing any of the animation's
+         * other settings. Calling this before an initial call to {@link #animateValueForChild} will
+         * work, but result in unknown values for stiffness, etc. and is not recommended.
+         */
+        private void updateValueForChild(
+                DynamicAnimation.ViewProperty property, View view, float position) {
+            if (view != null) {
+                final SpringAnimation animation =
+                        (SpringAnimation) view.getTag(getTagIdForProperty(property));
+                final SpringForce animationSpring = animation.getSpring();
+
+                if (animationSpring == null) {
+                    return;
+                }
+
+                animationSpring.setFinalPosition(position);
+                animation.start();
+            }
+        }
+
+        /**
+         * Configures the path animator to respect the settings passed into the animation builder
+         * and adds update listeners that update the translation physics animations. Then, starts
+         * the path animation.
+         */
+        protected void startPathAnimation() {
+            final SpringForce defaultSpringForceX = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_X, mView);
+            final SpringForce defaultSpringForceY = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_Y, mView);
+
+            if (mStartDelay > 0) {
+                mPathAnimator.setStartDelay(mStartDelay);
+            }
+
+            final Runnable updatePhysicsAnims = () -> {
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
+            };
+
+            mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
+            mPathAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_X,
+                            mView,
+                            mCurrentPointOnPath.x,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceX.getDampingRatio());
+
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_Y,
+                            mView,
+                            mCurrentPointOnPath.y,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceY.getDampingRatio());
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    updatePhysicsAnims.run();
+                }
+            });
+
+            // If there's a target animator saved for the view, make sure it's not running.
+            final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
+            if (targetAnimator != null) {
+                targetAnimator.cancel();
+            }
+
+            mView.setTag(R.id.target_animator_tag, mPathAnimator);
+            mPathAnimator.start();
+        }
+
         private void clearAnimator() {
             mInitialPropertyValues.clear();
             mAnimatedProperties.clear();
@@ -864,6 +1095,8 @@
             mStiffness = -1;
             mDampingRatio = -1;
             mEndActionsForProperty.clear();
+            mPathAnimator = null;
+            mPositionEndActions = null;
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index ab8752e4..2ec09a9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 
+import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FlingAnimation;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -33,6 +34,8 @@
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Set;
 
@@ -53,6 +56,10 @@
     /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
+    /** Values to use for animating bubbles in. */
+    private static final float ANIMATE_IN_STIFFNESS = 1000f;
+    private static final int ANIMATE_IN_START_DELAY = 25;
+
     /**
      * Values to use for the default {@link SpringForce} provided to the physics animation layout.
      */
@@ -92,7 +99,7 @@
     private boolean mStackMovedToStartPosition = false;
 
     /** The most recent position in which the stack was resting on the edge of the screen. */
-    private PointF mRestingStackPosition;
+    @Nullable private PointF mRestingStackPosition;
 
     /** The height of the most recently visible IME. */
     private float mImeHeight = 0f;
@@ -139,14 +146,16 @@
 
     /** Horizontal offset of bubbles in the stack. */
     private float mStackOffset;
-    /** Diameter of the bubbles themselves. */
-    private int mIndividualBubbleSize;
+    /** Diameter of the bubble icon. */
+    private int mBubbleIconBitmapSize;
+    /** Width of the bubble (icon and padding). */
+    private int mBubbleSize;
     /**
      * The amount of space to add between the bubbles and certain UI elements, such as the top of
      * the screen or the IME. This does not apply to the left/right sides of the screen since the
      * stack goes offscreen intentionally.
      */
-    private int mBubblePadding;
+    private int mBubblePaddingTop;
     /** How far offscreen the stack rests. */
     private int mBubbleOffscreen;
     /** How far down the screen the stack starts, when there is no pre-existing location. */
@@ -185,7 +194,7 @@
             return false;
         }
 
-        float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
+        float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
         float screenCenter = mLayout.getWidth() / 2;
         return stackCenter < screenCenter;
     }
@@ -197,18 +206,18 @@
      */
     public void springStack(float destinationX, float destinationY) {
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startXVelocity */,
-                    destinationX);
+                0 /* startXVelocity */,
+                destinationX);
 
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startYVelocity */,
-                    destinationY);
+                0 /* startYVelocity */,
+                destinationY);
     }
 
     /**
@@ -218,7 +227,7 @@
      * @return The X value that the stack will end up at after the fling/spring.
      */
     public float flingStackThenSpringToEdge(float x, float velX, float velY) {
-        final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+        final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
 
         final boolean stackShouldFlingLeft = stackOnLeftSide
                 ? velX < ESCAPE_VELOCITY
@@ -230,6 +239,12 @@
         final float destinationRelativeX = stackShouldFlingLeft
                 ? stackBounds.left : stackBounds.right;
 
+        // If all bubbles were removed during a drag event, just return the X we would have animated
+        // to if there were still bubbles.
+        if (mLayout == null || mLayout.getChildCount() == 0) {
+            return destinationRelativeX;
+        }
+
         // Minimum velocity required for the stack to make it to the targeted side of the screen,
         // taking friction into account (4.2f is the number that friction scalars are multiplied by
         // in DynamicAnimation.DragForce). This is an estimate - it could possibly be slightly off,
@@ -262,15 +277,6 @@
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
                 /* destination */ null);
 
-        mLayout.setEndActionForMultipleProperties(
-                () -> {
-                    mRestingStackPosition = new PointF();
-                    mRestingStackPosition.set(mStackPosition);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
-                },
-                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
         // If we're flinging now, there's no more touch event to catch up to.
         mFirstBubbleSpringingToTouch = false;
         mIsMovingFromFlinging = true;
@@ -304,6 +310,18 @@
         setStackPosition(new PointF(x, y));
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("StackAnimationController state:");
+        pw.print("  isActive:             "); pw.println(isActiveController());
+        pw.print("  restingStackPos:      ");
+        pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+        pw.print("  currentStackPos:      "); pw.println(mStackPosition.toString());
+        pw.print("  isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
+        pw.print("  withinDismiss:        "); pw.println(mWithinDismissTarget);
+        pw.print("  firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
+    }
+
     /**
      * Flings the first bubble along the given property's axis, using the provided configuration
      * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
@@ -317,7 +335,7 @@
             SpringForce spring,
             Float finalPosition) {
         Log.d(TAG, String.format("Flinging %s.",
-                        PhysicsAnimationLayout.getReadablePropertyName(property)));
+                PhysicsAnimationLayout.getReadablePropertyName(property)));
 
         StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
         final float currentValue = firstBubbleProperty.getValue(this);
@@ -347,6 +365,9 @@
 
                 .addEndListener((animation, canceled, endValue, endVelocity) -> {
                     if (!canceled) {
+                        mRestingStackPosition = new PointF();
+                        mRestingStackPosition.set(mStackPosition);
+
                         springFirstBubbleWithStackFollowing(property, spring, endVelocity,
                                 finalPosition != null
                                         ? finalPosition
@@ -368,8 +389,8 @@
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
 
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
     }
 
     /** Save the current IME height so that we know where the stack bounds should be. */
@@ -427,7 +448,7 @@
                                     : 0);
             allowableRegion.right =
                     mLayout.getWidth()
-                            - mIndividualBubbleSize
+                            - mBubbleSize
                             + mBubbleOffscreen
                             - Math.max(
                             insets.getSystemWindowInsetRight(),
@@ -436,7 +457,7 @@
                                     : 0);
 
             allowableRegion.top =
-                    mBubblePadding
+                    mBubblePaddingTop
                             + Math.max(
                             mStatusBarHeight,
                             insets.getDisplayCutout() != null
@@ -444,9 +465,9 @@
                                     : 0);
             allowableRegion.bottom =
                     mLayout.getHeight()
-                            - mIndividualBubbleSize
-                            - mBubblePadding
-                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePadding : 0f)
+                            - mBubbleSize
+                            - mBubblePaddingTop
+                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
                             - Math.max(
                             insets.getSystemWindowInsetBottom(),
                             insets.getDisplayCutout() != null
@@ -516,13 +537,19 @@
         mWithinDismissTarget = true;
         mFirstBubbleSpringingToTouch = false;
 
-        animationForChildAtIndex(0)
-                .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
-                .translationY(destY, after)
-                .withPositionStartVelocities(velX, velY)
-                .withStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .start();
+        springFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+                velX, mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f);
+
+        springFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+                velY, destY, after);
     }
 
     /**
@@ -550,7 +577,7 @@
      */
     protected void springFirstBubbleWithStackFollowing(
             DynamicAnimation.ViewProperty property, SpringForce spring,
-            float vel, float finalPosition) {
+            float vel, float finalPosition, @Nullable Runnable... after) {
 
         if (mLayout.getChildCount() == 0) {
             return;
@@ -564,6 +591,13 @@
         SpringAnimation springAnimation =
                 new SpringAnimation(this, firstBubbleProperty)
                         .setSpring(spring)
+                        .addEndListener((dynamicAnimation, b, v, v1) -> {
+                            if (after != null) {
+                                for (Runnable callback : after) {
+                                    callback.run();
+                                }
+                            }
+                        })
                         .setStartVelocity(vel);
 
         cancelStackPositionAnimation(property);
@@ -620,13 +654,18 @@
 
     @Override
     void onChildAdded(View child, int index) {
+        // Don't animate additions within the dismiss target.
+        if (mWithinDismissTarget) {
+            return;
+        }
+
         if (mLayout.getChildCount() == 1) {
             // If this is the first child added, position the stack in its starting position.
             moveStackToStartPosition();
         } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
             // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
             // to the back of the stack, it'll be largely invisible so don't bother animating it in.
-            animateInBubble(child);
+            animateInBubble(child, index);
         }
     }
 
@@ -641,24 +680,29 @@
                 .translationX(mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR))
                 .start();
 
+        // If there are other bubbles, pull them into the correct position.
         if (mLayout.getChildCount() > 0) {
             animationForChildAtIndex(0).translationX(mStackPosition.x).start();
         } else {
-            // Set the start position back to the default since we're out of bubbles. New bubbles
-            // will then animate in from the start position.
-            mStackPosition = getDefaultStartPosition();
+            // If there's no other bubbles, and we were in the dismiss target, reset the flag.
+            mWithinDismissTarget = false;
         }
     }
 
     @Override
-    void onChildReordered(View child, int oldIndex, int newIndex) {}
+    void onChildReordered(View child, int oldIndex, int newIndex) {
+        if (isStackPositionSet()) {
+            setStackPosition(mStackPosition);
+        }
+    }
 
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         Resources res = layout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
         mStackStartingVerticalOffset =
                 res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
@@ -666,6 +710,20 @@
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
     }
 
+    /**
+     * Update effective screen width based on current orientation.
+     * @param orientation Landscape or portrait.
+     */
+    public void updateOrientation(int orientation) {
+        if (mLayout != null) {
+            Resources res = mLayout.getContext().getResources();
+            mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            mStatusBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height);
+        }
+    }
+
+
     /** Moves the stack, without any animation, to the starting position. */
     private void moveStackToStartPosition() {
         // Post to ensure that the layout's width and height have been calculated.
@@ -679,7 +737,7 @@
 
             // Animate in the top bubble now that we're visible.
             if (mLayout.getChildCount() > 0) {
-                animateInBubble(mLayout.getChildAt(0));
+                animateInBubble(mLayout.getChildAt(0), 0 /* index */);
             }
         });
     }
@@ -715,7 +773,9 @@
 
         // If we're not the active controller, we don't want to physically move the bubble views.
         if (isActiveController()) {
-            mLayout.cancelAllAnimations();
+            // Cancel animations that could be moving the views.
+            mLayout.cancelAllAnimationsOfProperties(
+                    DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
             cancelStackPositionAnimations();
 
             // Since we're not using the chained animations, apply the offsets manually.
@@ -742,21 +802,34 @@
     }
 
     /** Animates in the given bubble. */
-    private void animateInBubble(View child) {
+    private void animateInBubble(View child, int index) {
         if (!isActiveController()) {
             return;
         }
 
-        child.setTranslationY(mStackPosition.y);
+        final float xOffset =
+                getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
 
-        float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+        // Position the new bubble in the correct position, scaled down completely.
+        child.setTranslationX(mStackPosition.x + xOffset * index);
+        child.setTranslationY(mStackPosition.y);
+        child.setScaleX(0f);
+        child.setScaleY(0f);
+
+        // Push the subsequent views out of the way, if there are subsequent views.
+        if (index + 1 < mLayout.getChildCount()) {
+            animationForChildAtIndex(index + 1)
+                    .translationX(mStackPosition.x + xOffset * (index + 1))
+                    .withStiffness(SpringForce.STIFFNESS_LOW)
+                    .start();
+        }
+
+        // Scale in the new bubble, slightly delayed.
         animationForChild(child)
-                .scaleX(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .scaleY(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .alpha(0f /* from */, 1f /* to */)
-                .translationX(
-                        mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset /* from */,
-                        mStackPosition.x /* to */)
+                .scaleX(1f)
+                .scaleY(1f)
+                .withStiffness(ANIMATE_IN_STIFFNESS)
+                .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
                 .start();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index fba0d50..20742d6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -43,7 +43,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
 
@@ -154,7 +154,7 @@
 
         updateConfiguration();
         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mKeyguardUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mKeyguardUpdateCallback);
     }
 
     private void updateConfiguration() {
@@ -556,7 +556,7 @@
         mSensorManager.unregisterListener(mSensorEventListener);
         mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
         Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mKeyguardUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mKeyguardUpdateCallback);
     }
 
     public Uri reportRejectedTouch() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 4120334..914258f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 
 import android.content.Context;
+import android.hardware.SensorManager;
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.DeviceConfig;
@@ -34,7 +35,8 @@
 import com.android.systemui.plugins.FalsingPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.io.PrintWriter;
 
@@ -50,19 +52,32 @@
 @Singleton
 public class FalsingManagerProxy implements FalsingManager {
 
+    private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
+
+    private final ProximitySensor mProximitySensor;
     private FalsingManager mInternalFalsingManager;
-    private final Handler mMainHandler;
+    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
+    private final DeviceConfigProxy mDeviceConfig;
     private boolean mBrightlineEnabled;
 
     @Inject
     FalsingManagerProxy(Context context, PluginManager pluginManager,
-            @Named(MAIN_HANDLER_NAME) Handler handler) {
-        mMainHandler = handler;
+            @Named(MAIN_HANDLER_NAME) Handler handler,
+            ProximitySensor proximitySensor,
+            DeviceConfigProxy deviceConfig) {
+        mProximitySensor = proximitySensor;
+        mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
+        mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME);
+        mDeviceConfig = deviceConfig;
+        mDeviceConfigListener =
+                properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace());
         setupFalsingManager(context);
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                command -> mMainHandler.post(command),
-                properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace())
+        mDeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                handler::post,
+                mDeviceConfigListener
         );
+
         final PluginListener<FalsingPlugin> mPluginListener = new PluginListener<FalsingPlugin>() {
             public void onPluginConnected(FalsingPlugin plugin, Context context) {
                 FalsingManager pluginFalsingManager = plugin.getFalsingManager(context);
@@ -91,9 +106,8 @@
     /**
      * Chooses the FalsingManager implementation.
      */
-    @VisibleForTesting
-    public void setupFalsingManager(Context context) {
-        boolean brightlineEnabled = DeviceConfig.getBoolean(
+    private void setupFalsingManager(Context context) {
+        boolean brightlineEnabled = mDeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI, BRIGHTLINE_FALSING_MANAGER_ENABLED, true);
         if (brightlineEnabled == mBrightlineEnabled && mInternalFalsingManager != null) {
             return;
@@ -108,11 +122,11 @@
         } else {
             mInternalFalsingManager = new BrightLineFalsingManager(
                     new FalsingDataProvider(context.getResources().getDisplayMetrics()),
-                    Dependency.get(AsyncSensorManager.class),
-                    KeyguardUpdateMonitor.getInstance(context)
+                    Dependency.get(KeyguardUpdateMonitor.class),
+                    mProximitySensor,
+                    mDeviceConfig
             );
         }
-
     }
 
     /**
@@ -305,6 +319,7 @@
 
     @Override
     public void cleanup() {
+        mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
         mInternalFalsingManager.cleanup();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 9e0b702..bd0906a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -19,10 +19,6 @@
 import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_REMAIN_LOCKED;
 import static com.android.systemui.classifier.FalsingManagerImpl.FALSING_SUCCESS;
 
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.net.Uri;
 import android.util.Log;
@@ -33,12 +29,13 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.Locale;
 
 /**
  * FalsingManager designed to make clear why a touch was rejected.
@@ -46,11 +43,11 @@
 public class BrightLineFalsingManager implements FalsingManager {
 
     static final boolean DEBUG = false;
-    private static final String TAG = "FalsingManagerPlugin";
+    private static final String TAG = "FalsingManager";
 
-    private final SensorManager mSensorManager;
     private final FalsingDataProvider mDataProvider;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final ProximitySensor mProximitySensor;
     private boolean mSessionStarted;
     private MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
@@ -58,20 +55,9 @@
     private boolean mScreenOn;
     private boolean mJustUnlockedWithFace;
 
-    private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
-
     private final List<FalsingClassifier> mClassifiers;
 
-    private SensorEventListener mSensorEventListener = new SensorEventListener() {
-        @Override
-        public synchronized void onSensorChanged(SensorEvent event) {
-            onSensorEvent(event);
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-    };
+    private ProximitySensor.ProximitySensorListener mSensorEventListener = this::onProximityEvent;
 
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -87,45 +73,35 @@
 
     public BrightLineFalsingManager(
             FalsingDataProvider falsingDataProvider,
-            SensorManager sensorManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            ProximitySensor proximitySensor,
+            DeviceConfigProxy deviceConfigProxy) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDataProvider = falsingDataProvider;
-        mSensorManager = sensorManager;
+        mProximitySensor = proximitySensor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
         mMetricsLogger = new MetricsLogger();
         mClassifiers = new ArrayList<>();
-        DistanceClassifier distanceClassifier = new DistanceClassifier(mDataProvider);
-        ProximityClassifier proximityClassifier = new ProximityClassifier(distanceClassifier,
-                mDataProvider);
+        DistanceClassifier distanceClassifier =
+                new DistanceClassifier(mDataProvider, deviceConfigProxy);
+        ProximityClassifier proximityClassifier =
+                new ProximityClassifier(distanceClassifier, mDataProvider, deviceConfigProxy);
         mClassifiers.add(new PointerCountClassifier(mDataProvider));
         mClassifiers.add(new TypeClassifier(mDataProvider));
-        mClassifiers.add(new DiagonalClassifier(mDataProvider));
+        mClassifiers.add(new DiagonalClassifier(mDataProvider, deviceConfigProxy));
         mClassifiers.add(distanceClassifier);
         mClassifiers.add(proximityClassifier);
-        mClassifiers.add(new ZigZagClassifier(mDataProvider));
+        mClassifiers.add(new ZigZagClassifier(mDataProvider, deviceConfigProxy));
     }
 
     private void registerSensors() {
-        Sensor s = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-        if (s != null) {
-            // This can be expensive, and doesn't need to happen on the main thread.
-            mBackgroundExecutor.submit(() -> {
-                logDebug("registering sensor listener");
-                mSensorManager.registerListener(
-                        mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
-            });
-        }
+        mProximitySensor.register(mSensorEventListener);
     }
 
 
     private void unregisterSensors() {
-        // This can be expensive, and doesn't need to happen on the main thread.
-        mBackgroundExecutor.submit(() -> {
-            logDebug("unregistering sensor listener");
-            mSensorManager.unregisterListener(mSensorEventListener);
-        });
+        mProximitySensor.unregister(mSensorEventListener);
     }
 
     private void sessionStart() {
@@ -167,7 +143,15 @@
         boolean r = !mJustUnlockedWithFace && mClassifiers.stream().anyMatch(falsingClassifier -> {
             boolean result = falsingClassifier.isFalseTouch();
             if (result) {
-                logInfo(falsingClassifier.getClass().getName() + ": true");
+                logInfo(String.format(
+                        (Locale) null,
+                        "{classifier=%s, interactionType=%d}",
+                        falsingClassifier.getClass().getName(),
+                        mDataProvider.getInteractionType()));
+                String reason = falsingClassifier.getReason();
+                if (reason != null) {
+                    logInfo(reason);
+                }
             } else {
                 logDebug(falsingClassifier.getClass().getName() + ": false");
             }
@@ -187,10 +171,10 @@
         mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent));
     }
 
-    private void onSensorEvent(SensorEvent sensorEvent) {
+    private void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) {
         // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
         // make these calls.
-        mClassifiers.forEach((classifier) -> classifier.onSensorEvent(sensorEvent));
+        mClassifiers.forEach((classifier) -> classifier.onProximityEvent(proximityEvent));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java
index cc66454..520e0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java
@@ -23,6 +23,10 @@
 
 import android.provider.DeviceConfig;
 
+import com.android.systemui.util.DeviceConfigProxy;
+
+import java.util.Locale;
+
 /**
  * False on swipes that are too close to 45 degrees.
  *
@@ -42,14 +46,14 @@
     private final float mHorizontalAngleRange;
     private final float mVerticalAngleRange;
 
-    DiagonalClassifier(FalsingDataProvider dataProvider) {
+    DiagonalClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
         super(dataProvider);
 
-        mHorizontalAngleRange = DeviceConfig.getFloat(
+        mHorizontalAngleRange = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE,
                 HORIZONTAL_ANGLE_RANGE);
-        mVerticalAngleRange = DeviceConfig.getFloat(
+        mVerticalAngleRange = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE,
                 VERTICAL_ANGLE_RANGE);
@@ -82,6 +86,15 @@
                 maxAngle + ONE_HUNDRED_EIGHTY_DEG);
     }
 
+    @Override
+    String getReason() {
+        return String.format(
+                (Locale) null,
+                "{angle=%f, vertical=%s}",
+                getAngle(),
+                isVertical());
+    }
+
     private boolean angleBetween(float angle, float min, float max) {
         // No need to normalize angle as it is guaranteed to be between 0 and 2*PI.
         min = normalizeAngle(min);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java
index a6a617d..7f45cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java
@@ -27,7 +27,10 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 
+import com.android.systemui.util.DeviceConfigProxy;
+
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Ensure that the swipe + momentum covers a minimum distance.
@@ -50,35 +53,35 @@
     private boolean mDistanceDirty;
     private DistanceVectors mCachedDistance;
 
-    DistanceClassifier(FalsingDataProvider dataProvider) {
+    DistanceClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
         super(dataProvider);
 
-        mVelocityToDistanceMultiplier = DeviceConfig.getFloat(
+        mVelocityToDistanceMultiplier = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_VELOCITY_TO_DISTANCE,
                 VELOCITY_TO_DISTANCE);
 
-        float horizontalFlingThresholdIn = DeviceConfig.getFloat(
+        float horizontalFlingThresholdIn = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_FLING_THRESHOLD_IN,
                 HORIZONTAL_FLING_THRESHOLD_DISTANCE_IN);
 
-        float verticalFlingThresholdIn = DeviceConfig.getFloat(
+        float verticalFlingThresholdIn = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_VERTICAL_FLING_THRESHOLD_IN,
                 VERTICAL_FLING_THRESHOLD_DISTANCE_IN);
 
-        float horizontalSwipeThresholdIn = DeviceConfig.getFloat(
+        float horizontalSwipeThresholdIn = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_HORIZONTAL_SWIPE_THRESHOLD_IN,
                 HORIZONTAL_SWIPE_THRESHOLD_DISTANCE_IN);
 
-        float verticalSwipeThresholdIn = DeviceConfig.getFloat(
+        float verticalSwipeThresholdIn = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_VERTICAL_SWIPE_THRESHOLD_IN,
                 VERTICAL_SWIPE_THRESHOLD_DISTANCE_IN);
 
-        float screenFractionMaxDistance = DeviceConfig.getFloat(
+        float screenFractionMaxDistance = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_DISTANCE_SCREEN_FRACTION_MAX_DISTANCE,
                 SCREEN_FRACTION_MAX_DISTANCE);
@@ -142,15 +145,66 @@
 
     @Override
     public boolean isFalseTouch() {
-        return !getDistances().getPassedFlingThreshold();
+        return !getPassedFlingThreshold();
+    }
+
+    @Override
+    String getReason() {
+        DistanceVectors distanceVectors = getDistances();
+
+        return String.format(
+                (Locale) null,
+                "{distanceVectors=%s, isHorizontal=%s, velocityToDistanceMultiplier=%f, "
+                        + "horizontalFlingThreshold=%f, verticalFlingThreshold=%f, "
+                        + "horizontalSwipeThreshold=%f, verticalSwipeThreshold=%s}",
+                distanceVectors,
+                isHorizontal(),
+                mVelocityToDistanceMultiplier,
+                mHorizontalFlingThresholdPx,
+                mVerticalFlingThresholdPx,
+                mHorizontalSwipeThresholdPx,
+                mVerticalSwipeThresholdPx);
     }
 
     boolean isLongSwipe() {
-        boolean longSwipe = getDistances().getPassedDistanceThreshold();
+        boolean longSwipe = getPassedDistanceThreshold();
         logDebug("Is longSwipe? " + longSwipe);
         return longSwipe;
     }
 
+    private boolean getPassedDistanceThreshold() {
+        DistanceVectors distanceVectors = getDistances();
+        if (isHorizontal()) {
+            logDebug("Horizontal swipe distance: " + Math.abs(distanceVectors.mDx));
+            logDebug("Threshold: " + mHorizontalSwipeThresholdPx);
+
+            return Math.abs(distanceVectors.mDx) >= mHorizontalSwipeThresholdPx;
+        }
+
+        logDebug("Vertical swipe distance: " + Math.abs(distanceVectors.mDy));
+        logDebug("Threshold: " + mVerticalSwipeThresholdPx);
+        return Math.abs(distanceVectors.mDy) >= mVerticalSwipeThresholdPx;
+    }
+
+    private boolean getPassedFlingThreshold() {
+        DistanceVectors distanceVectors = getDistances();
+
+        float dX = distanceVectors.mDx + distanceVectors.mVx * mVelocityToDistanceMultiplier;
+        float dY = distanceVectors.mDy + distanceVectors.mVy * mVelocityToDistanceMultiplier;
+
+        if (isHorizontal()) {
+            logDebug("Horizontal swipe and fling distance: " + distanceVectors.mDx + ", "
+                    + distanceVectors.mVx * mVelocityToDistanceMultiplier);
+            logDebug("Threshold: " + mHorizontalFlingThresholdPx);
+            return Math.abs(dX) >= mHorizontalFlingThresholdPx;
+        }
+
+        logDebug("Vertical swipe and fling distance: " + distanceVectors.mDy + ", "
+                + distanceVectors.mVy * mVelocityToDistanceMultiplier);
+        logDebug("Threshold: " + mVerticalFlingThresholdPx);
+        return Math.abs(dY) >= mVerticalFlingThresholdPx;
+    }
+
     private class DistanceVectors {
         final float mDx;
         final float mDy;
@@ -164,34 +218,9 @@
             this.mVy = vY;
         }
 
-        boolean getPassedDistanceThreshold() {
-            if (isHorizontal()) {
-                logDebug("Horizontal swipe distance: " + Math.abs(mDx));
-                logDebug("Threshold: " + mHorizontalSwipeThresholdPx);
-
-                return Math.abs(mDx) >= mHorizontalSwipeThresholdPx;
-            }
-
-            logDebug("Vertical swipe distance: " + Math.abs(mDy));
-            logDebug("Threshold: " + mVerticalSwipeThresholdPx);
-            return Math.abs(mDy) >= mVerticalSwipeThresholdPx;
-        }
-
-        boolean getPassedFlingThreshold() {
-            float dX = this.mDx + this.mVx * mVelocityToDistanceMultiplier;
-            float dY = this.mDy + this.mVy * mVelocityToDistanceMultiplier;
-
-            if (isHorizontal()) {
-                logDebug("Horizontal swipe and fling distance: " + this.mDx + ", "
-                        + this.mVx * mVelocityToDistanceMultiplier);
-                logDebug("Threshold: " + mHorizontalFlingThresholdPx);
-                return Math.abs(dX) >= mHorizontalFlingThresholdPx;
-            }
-
-            logDebug("Vertical swipe and fling distance: " + this.mDy + ", "
-                    + this.mVy * mVelocityToDistanceMultiplier);
-            logDebug("Threshold: " + mVerticalFlingThresholdPx);
-            return Math.abs(dY) >= mVerticalFlingThresholdPx;
+        @Override
+        public String toString() {
+            return String.format((Locale) null, "{dx=%f, vx=%f, dy=%f, vy=%f}", mDx, mVx, mDy, mVy);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
index 685e7c5..7555051 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.classifier.brightline;
 
-import android.hardware.SensorEvent;
 import android.view.MotionEvent;
 
 import com.android.systemui.classifier.Classifier;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.util.List;
 
@@ -98,9 +98,9 @@
     void onTouchEvent(MotionEvent motionEvent) {};
 
     /**
-     * Called whenever a SensorEvent occurs, specifically the ProximitySensor.
+     * Called when a ProximityEvent occurs (change in near/far).
      */
-    void onSensorEvent(SensorEvent sensorEvent) {};
+    void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) {};
 
     /**
      * The phone screen has turned on and we need to begin falsing detection.
@@ -117,6 +117,14 @@
      */
     abstract boolean isFalseTouch();
 
+    /**
+     * Give the classifier a chance to log more details about why it triggered.
+     *
+     * This should only be called after a call to {@link #isFalseTouch()}, and only if
+     * {@link #isFalseTouch()} returns true;
+     */
+    abstract String getReason();
+
     static void logDebug(String msg) {
         BrightLineFalsingManager.logDebug(msg);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
index 40e141f..85a4d23 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
@@ -18,6 +18,8 @@
 
 import android.view.MotionEvent;
 
+import java.util.Locale;
+
 /**
  * False touch if more than one finger touches the screen.
  *
@@ -50,4 +52,13 @@
     public boolean isFalseTouch() {
         return mMaxPointerCount > MAX_ALLOWED_POINTERS;
     }
+
+    @Override
+    String getReason() {
+        return String.format(
+                (Locale) null,
+                "{pointersObserved=%d, threshold=%d}",
+                mMaxPointerCount,
+                MAX_ALLOWED_POINTERS);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
index 2644bf9..749914e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java
@@ -19,11 +19,14 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
 import android.provider.DeviceConfig;
 import android.view.MotionEvent;
 
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.sensors.ProximitySensor;
+
+import java.util.Locale;
+
 
 /**
  * False touch if proximity sensor is covered for more than a certain percentage of the gesture.
@@ -44,11 +47,11 @@
     private float mPercentNear;
 
     ProximityClassifier(DistanceClassifier distanceClassifier,
-            FalsingDataProvider dataProvider) {
+            FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
         super(dataProvider);
         this.mDistanceClassifier = distanceClassifier;
 
-        mPercentCoveredThreshold = DeviceConfig.getFloat(
+        mPercentCoveredThreshold = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD,
                 PERCENT_COVERED_THRESHOLD);
@@ -97,14 +100,12 @@
     }
 
     @Override
-    public void onSensorEvent(SensorEvent sensorEvent) {
-        if (sensorEvent.sensor.getType() == Sensor.TYPE_PROXIMITY) {
-            logDebug("Sensor is: " + (sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange())
-                    + " at time " + sensorEvent.timestamp);
-            update(
-                    sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange(),
-                    sensorEvent.timestamp);
-        }
+    public void onProximityEvent(
+            ProximitySensor.ProximityEvent proximityEvent) {
+        boolean near = proximityEvent.getNear();
+        long timestampNs = proximityEvent.getTimestampNs();
+        logDebug("Sensor is: " + near + " at time " + timestampNs);
+        update(near, timestampNs);
     }
 
     @Override
@@ -122,6 +123,16 @@
         return false;
     }
 
+    @Override
+    String getReason() {
+        return String.format(
+                (Locale) null,
+                "{percentInProximity=%f, threshold=%f, distanceClassifier=%s}",
+                mPercentNear,
+                mPercentCoveredThreshold,
+                mDistanceClassifier.getReason());
+    }
+
     /**
      * @param near        is the sensor showing the near state right now
      * @param timeStampNs time of this event in nanoseconds
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java
index b6ceab5..5f1b37a 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/TypeClassifier.java
@@ -58,4 +58,9 @@
                 return true;
         }
     }
+
+    @Override
+    String getReason() {
+        return String.format("{vertical=%s, up=%s, right=%s}", isVertical(), isUp(), isRight());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java
index 82ae30a..957ea8d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java
@@ -25,8 +25,11 @@
 import android.provider.DeviceConfig;
 import android.view.MotionEvent;
 
+import com.android.systemui.util.DeviceConfigProxy;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Penalizes gestures that change direction in either the x or y too much.
@@ -47,26 +50,30 @@
     private final float mMaxYPrimaryDeviance;
     private final float mMaxXSecondaryDeviance;
     private final float mMaxYSecondaryDeviance;
+    private float mLastDevianceX;
+    private float mLastDevianceY;
+    private float mLastMaxXDeviance;
+    private float mLastMaxYDeviance;
 
-    ZigZagClassifier(FalsingDataProvider dataProvider) {
+    ZigZagClassifier(FalsingDataProvider dataProvider, DeviceConfigProxy deviceConfigProxy) {
         super(dataProvider);
 
-        mMaxXPrimaryDeviance = DeviceConfig.getFloat(
+        mMaxXPrimaryDeviance = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_ZIGZAG_X_PRIMARY_DEVIANCE,
                 MAX_X_PRIMARY_DEVIANCE);
 
-        mMaxYPrimaryDeviance = DeviceConfig.getFloat(
+        mMaxYPrimaryDeviance = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE,
                 MAX_Y_PRIMARY_DEVIANCE);
 
-        mMaxXSecondaryDeviance = DeviceConfig.getFloat(
+        mMaxXSecondaryDeviance = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE,
                 MAX_X_SECONDARY_DEVIANCE);
 
-        mMaxYSecondaryDeviance = DeviceConfig.getFloat(
+        mMaxYSecondaryDeviance = deviceConfigProxy.getFloat(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE,
                 MAX_Y_SECONDARY_DEVIANCE);
@@ -137,11 +144,25 @@
             maxYDeviance = mMaxYPrimaryDeviance * totalDistanceIn * getYdpi();
         }
 
+        // These values are saved for logging reasons. {@see #getReason()}
+        mLastDevianceX = devianceX;
+        mLastDevianceY = devianceY;
+        mLastMaxXDeviance = maxXDeviance;
+        mLastMaxYDeviance = maxYDeviance;
+
         logDebug("Straightness Deviance: (" + devianceX + "," + devianceY + ") vs "
                 + "(" + maxXDeviance + "," + maxYDeviance + ")");
         return devianceX > maxXDeviance || devianceY > maxYDeviance;
     }
 
+    @Override
+    String getReason() {
+        return String.format(
+                (Locale) null,
+                "{devianceX=%f, maxDevianceX=%s, devianceY=%s, maxDevianceY=%s}",
+                mLastDevianceX, mLastMaxXDeviance, mLastDevianceY, mLastMaxYDeviance);
+    }
+
     private float getAtan2LastPoint() {
         MotionEvent firstEvent = getFirstMotionEvent();
         MotionEvent lastEvent = getLastMotionEvent();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index e5a54b8..abd41d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
 
 /**
  * Controls removing Keyguard authorization when the phone goes to sleep.
@@ -28,7 +29,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public DozeAuthRemover(Context context) {
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 86d4a48..4d3dc70 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -29,11 +29,12 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -45,7 +46,7 @@
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
     public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) {
         Context context = dozeService;
-        SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
+        AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
         DockManager dockManager = Dependency.get(DockManager.class);
         WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -91,20 +92,21 @@
                 params.getPolicy());
     }
 
-    private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
+    private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
             DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
             DockManager dockManager) {
         boolean allowPulseTriggers = true;
         return new DozeTriggers(context, machine, host, alarmManager, config, params,
-                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
+                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
+                new ProximitySensor(context, sensorManager));
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
             DozeMachine machine, Handler handler, AlarmManager alarmManager,
             DozeParameters params) {
         return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params,
-                KeyguardUpdateMonitor.getInstance(context));
+                Dependency.get(KeyguardUpdateMonitor.class));
     }
 
     public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index c09e284..8fe9f92 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -23,6 +23,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -112,7 +113,7 @@
                 }
                 log("init");
                 if (sRegisterKeyguardCallback) {
-                    KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
+                    Dependency.get(KeyguardUpdateMonitor.class).registerCallback(sKeyguardCallback);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 026a625..dfd83a5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
@@ -43,11 +42,10 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.R;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AlarmTimeout;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
@@ -62,7 +60,7 @@
 
     private final Context mContext;
     private final AlarmManager mAlarmManager;
-    private final SensorManager mSensorManager;
+    private final AsyncSensorManager mSensorManager;
     private final ContentResolver mResolver;
     private final TriggerSensor mPickupSensor;
     private final DozeParameters mDozeParameters;
@@ -74,13 +72,13 @@
     protected TriggerSensor[] mSensors;
 
     private final Handler mHandler = new Handler();
-    private final ProxSensor mProxSensor;
+    private final ProximitySensor mProximitySensor;
     private long mDebounceFrom;
     private boolean mSettingRegistered;
     private boolean mListening;
     private boolean mPaused;
 
-    public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
+    public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
             DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
             Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
         mContext = context;
@@ -91,6 +89,7 @@
         mWakeLock = wakeLock;
         mProxCallback = proxCallback;
         mResolver = mContext.getContentResolver();
+        mCallback = callback;
 
         boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
         mSensors = new TriggerSensor[] {
@@ -146,8 +145,10 @@
                         false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
         };
 
-        mProxSensor = new ProxSensor(policy);
-        mCallback = callback;
+        mProximitySensor = new ProximitySensor(context, sensorManager);
+
+        mProximitySensor.register(
+                proximityEvent -> mProxCallback.accept(!proximityEvent.getNear()));
     }
 
     /**
@@ -236,7 +237,15 @@
     }
 
     public void setProxListening(boolean listen) {
-        mProxSensor.setRequested(listen);
+        if (mProximitySensor.isRegistered() && listen) {
+            mProximitySensor.alertListeners();
+        } else {
+            if (listen) {
+                mProximitySensor.resume();
+            } else {
+                mProximitySensor.pause();
+            }
+        }
     }
 
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@@ -267,115 +276,16 @@
     /** Dump current state */
     public void dump(PrintWriter pw) {
         for (TriggerSensor s : mSensors) {
-            pw.print("  Sensor: "); pw.println(s.toString());
+            pw.println("  Sensor: " + s.toString());
         }
-        pw.print("  ProxSensor: "); pw.println(mProxSensor.toString());
+        pw.println("  ProxSensor: " + mProximitySensor.toString());
     }
 
     /**
-     * @return true if prox is currently far, false if near or null if unknown.
+     * @return true if prox is currently near, false if far or null if unknown.
      */
-    public Boolean isProximityCurrentlyFar() {
-        return mProxSensor.mCurrentlyFar;
-    }
-
-    private class ProxSensor implements SensorEventListener {
-
-        boolean mRequested;
-        boolean mRegistered;
-        Boolean mCurrentlyFar;
-        long mLastNear;
-        final AlarmTimeout mCooldownTimer;
-        final AlwaysOnDisplayPolicy mPolicy;
-        final Sensor mSensor;
-        final boolean mUsingBrightnessSensor;
-
-        public ProxSensor(AlwaysOnDisplayPolicy policy) {
-            mPolicy = policy;
-            mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
-                    "prox_cooldown", mHandler);
-
-            // The default prox sensor can be noisy, so let's use a prox gated brightness sensor
-            // if available.
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            mUsingBrightnessSensor = sensor != null;
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            mSensor = sensor;
-        }
-
-        void setRequested(boolean requested) {
-            if (mRequested == requested) {
-                // Send an update even if we don't re-register.
-                mHandler.post(() -> {
-                    if (mCurrentlyFar != null) {
-                        mProxCallback.accept(mCurrentlyFar);
-                    }
-                });
-                return;
-            }
-            mRequested = requested;
-            updateRegistered();
-        }
-
-        private void updateRegistered() {
-            setRegistered(mRequested && !mCooldownTimer.isScheduled());
-        }
-
-        private void setRegistered(boolean register) {
-            if (mRegistered == register) {
-                return;
-            }
-            if (register) {
-                mRegistered = mSensorManager.registerListener(this, mSensor,
-                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
-            } else {
-                mSensorManager.unregisterListener(this);
-                mRegistered = false;
-                mCurrentlyFar = null;
-            }
-        }
-
-        @Override
-        public void onSensorChanged(android.hardware.SensorEvent event) {
-            if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
-
-            if (mUsingBrightnessSensor) {
-                // The custom brightness sensor is gated by the proximity sensor and will return 0
-                // whenever prox is covered.
-                mCurrentlyFar = event.values[0] > 0;
-            } else {
-                mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
-            }
-            mProxCallback.accept(mCurrentlyFar);
-
-            long now = SystemClock.elapsedRealtime();
-            if (mCurrentlyFar == null) {
-                // Sensor has been unregistered by the proxCallback. Do nothing.
-            } else if (!mCurrentlyFar) {
-                mLastNear = now;
-            } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
-                // If the last near was very recent, we might be using more power for prox
-                // wakeups than we're saving from turning of the screen. Instead, turn it off
-                // for a while.
-                mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
-                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
-                updateRegistered();
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-
-        @Override
-        public String toString() {
-            return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s,"
-                    + " sensor=%s}", mRegistered, mRequested, mCooldownTimer.isScheduled(),
-                    mCurrentlyFar, mSensor);
-        }
+    public Boolean isProximityCurrentlyNear() {
+        return mProximitySensor.isNear();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00bfb3f..8eed71c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -24,10 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -39,16 +35,16 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
-import java.util.function.IntConsumer;
+import java.util.function.Consumer;
 
 /**
  * Handles triggers for ambient state changes.
@@ -67,20 +63,22 @@
      */
     private static boolean sWakeDisplaySensorState = true;
 
+    private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500;
+
     private final Context mContext;
     private final DozeMachine mMachine;
     private final DozeSensors mDozeSensors;
     private final DozeHost mDozeHost;
     private final AmbientDisplayConfiguration mConfig;
     private final DozeParameters mDozeParameters;
-    private final SensorManager mSensorManager;
-    private final Handler mHandler;
+    private final AsyncSensorManager mSensorManager;
     private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
     private final DockEventListener mDockEventListener = new DockEventListener();
     private final DockManager mDockManager;
+    private final ProximitySensor.ProximityCheck mProxCheck;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
@@ -89,15 +87,15 @@
 
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
-            DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
-            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
+            DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
+            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
+            ProximitySensor proximitySensor) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
         mConfig = config;
         mDozeParameters = dozeParameters;
         mSensorManager = sensorManager;
-        mHandler = handler;
         mWakeLock = wakeLock;
         mAllowPulseTriggers = allowPulseTriggers;
         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
@@ -105,6 +103,7 @@
                 dozeParameters.getPolicy());
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
+        mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
     }
 
     private void onNotification(Runnable onPulseSuppressedListener) {
@@ -134,25 +133,27 @@
         }
     }
 
-    private void proximityCheckThenCall(IntConsumer callback,
+    private void proximityCheckThenCall(Consumer<Boolean> callback,
             boolean alreadyPerformedProxCheck,
             int reason) {
-        Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar();
+        Boolean cachedProxNear = mDozeSensors.isProximityCurrentlyNear();
         if (alreadyPerformedProxCheck) {
-            callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
-        } else if (cachedProxFar != null) {
-            callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR);
+            callback.accept(null);
+        } else if (cachedProxNear != null) {
+            callback.accept(cachedProxNear);
         } else {
             final long start = SystemClock.uptimeMillis();
-            new ProximityCheck() {
-                @Override
-                public void onProximityResult(int result) {
-                    final long end = SystemClock.uptimeMillis();
-                    DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
-                            end - start, reason);
-                    callback.accept(result);
-                }
-            }.check();
+            mProxCheck.check(PROXIMITY_TIMEOUT_DELAY_MS, near -> {
+                final long end = SystemClock.uptimeMillis();
+                DozeLog.traceProximityResult(
+                        mContext,
+                        near == null ? false : near,
+                        end - start,
+                        reason);
+                callback.accept(near);
+                mWakeLock.release(TAG);
+            });
+            mWakeLock.acquire(TAG);
         }
     }
 
@@ -178,7 +179,7 @@
             }
         } else {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result != null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -267,7 +268,7 @@
 
         if (wake) {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result !=  null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -376,7 +377,7 @@
 
         mPulsePending = true;
         proximityCheckThenCall((result) -> {
-            if (result == ProximityCheck.RESULT_NEAR) {
+            if (result != null && result) {
                 // in pocket, abort pulse
                 DozeLog.tracePulseDropped(mContext, "inPocket");
                 mPulsePending = false;
@@ -412,89 +413,11 @@
         pw.print(" notificationPulseTime=");
         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
 
-        pw.print(" pulsePending="); pw.println(mPulsePending);
+        pw.println(" pulsePending=" + mPulsePending);
         pw.println("DozeSensors:");
         mDozeSensors.dump(pw);
     }
 
-    private abstract class ProximityCheck implements SensorEventListener, Runnable {
-        private static final int TIMEOUT_DELAY_MS = 500;
-
-        protected static final int RESULT_UNKNOWN = 0;
-        protected static final int RESULT_NEAR = 1;
-        protected static final int RESULT_FAR = 2;
-        protected static final int RESULT_NOT_CHECKED = 3;
-
-        private boolean mRegistered;
-        private boolean mFinished;
-        private float mMaxRange;
-
-        protected abstract void onProximityResult(int result);
-
-        public void check() {
-            Preconditions.checkState(!mFinished && !mRegistered);
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            if (sensor == null) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
-                finishWithResult(RESULT_UNKNOWN);
-                return;
-            }
-            mDozeSensors.setDisableSensorsInterferingWithProximity(true);
-
-            mMaxRange = sensor.getMaximumRange();
-            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
-                    mHandler);
-            mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
-            mWakeLock.acquire(TAG);
-            mRegistered = true;
-        }
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (event.values.length == 0) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!");
-                finishWithResult(RESULT_UNKNOWN);
-            } else {
-                if (DozeMachine.DEBUG) {
-                    Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
-                }
-                final boolean isNear = event.values[0] < mMaxRange;
-                finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
-            }
-        }
-
-        @Override
-        public void run() {
-            if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
-            finishWithResult(RESULT_UNKNOWN);
-        }
-
-        private void finishWithResult(int result) {
-            if (mFinished) return;
-            boolean wasRegistered = mRegistered;
-            if (mRegistered) {
-                mHandler.removeCallbacks(this);
-                mSensorManager.unregisterListener(this);
-                mDozeSensors.setDisableSensorsInterferingWithProximity(false);
-                mRegistered = false;
-            }
-            onProximityResult(result);
-            if (wasRegistered) {
-                mWakeLock.release(TAG);
-            }
-            mFinished = true;
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // noop
-        }
-    }
-
     private class TriggerReceiver extends BroadcastReceiver {
         private boolean mRegistered;
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3f598ff..57a5ae635 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -96,8 +96,8 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.leak.RotationUtils;
 import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
@@ -213,14 +213,17 @@
         Dependency.get(ConfigurationController.class).addCallback(this);
 
         mActivityStarter = Dependency.get(ActivityStarter.class);
-        UnlockMethodCache unlockMethodCache = UnlockMethodCache.getInstance(context);
-        unlockMethodCache.addListener(
-                () -> {
-                    if (mDialog != null && mDialog.mPanelController != null) {
-                        boolean locked = !unlockMethodCache.canSkipBouncer();
-                        mDialog.mPanelController.onDeviceLockStateChanged(locked);
-                    }
-                });
+        KeyguardStateController keyguardStateController =
+                Dependency.get(KeyguardStateController.class);
+        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+            @Override
+            public void onUnlockedChanged() {
+                if (mDialog != null && mDialog.mPanelController != null) {
+                    boolean locked = !keyguardStateController.canDismissLockScreen();
+                    mDialog.mPanelController.onDeviceLockStateChanged(locked);
+                }
+            }
+        });
     }
 
     /**
@@ -614,7 +617,7 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler);
+                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
                     MetricsLogger.action(mContext,
                             MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                 }
@@ -665,8 +668,7 @@
                         // Take an "interactive" bugreport.
                         MetricsLogger.action(mContext,
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        ActivityManager.getService().requestBugReport(
-                                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+                        ActivityManager.getService().requestInteractiveBugReport();
                     } catch (RemoteException e) {
                     }
                 }
@@ -683,8 +685,7 @@
             try {
                 // Take a "full" bugreport.
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestBugReport(
-                        ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 1f3403b..b9fe827 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -40,14 +40,14 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
     private final Context mContext;
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private GlobalActionsDialog mGlobalActions;
@@ -55,7 +55,7 @@
 
     public GlobalActionsImpl(Context context) {
         mContext = context;
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
         SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallback(this);
         mPanelExtension = Dependency.get(ExtensionController.class)
@@ -79,10 +79,10 @@
         if (mGlobalActions == null) {
             mGlobalActions = new GlobalActionsDialog(mContext, manager);
         }
-        mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
+        mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPanelExtension.get());
-        KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();
+        Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index aac721e..6b5a780 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -63,6 +63,7 @@
     // Below two constants make drawing at low priority, so other things can preempt our drawing.
     private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
     private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
+    private static final boolean DEBUG = true;
 
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
@@ -146,6 +147,10 @@
      * @return true if EglSurface is ready.
      */
     public boolean createEglSurface(SurfaceHolder surfaceHolder) {
+        if (DEBUG) {
+            Log.d(TAG, "createEglSurface start");
+        }
+
         if (hasEglDisplay()) {
             mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
         } else {
@@ -163,6 +168,9 @@
             return false;
         }
 
+        if (DEBUG) {
+            Log.d(TAG, "createEglSurface done");
+        }
         return true;
     }
 
@@ -190,6 +198,10 @@
      * @return true if EglContext is ready.
      */
     public boolean createEglContext() {
+        if (DEBUG) {
+            Log.d(TAG, "createEglContext start");
+        }
+
         int[] attrib_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2,
                 EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_NONE};
         if (hasEglDisplay()) {
@@ -203,6 +215,10 @@
             Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
             return false;
         }
+
+        if (DEBUG) {
+            Log.d(TAG, "createEglContext done");
+        }
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
index b154e66..99c55f1 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.util.Log;
 
 import com.android.systemui.Interpolators;
 
@@ -30,6 +31,7 @@
     private static final String TAG = ImageRevealHelper.class.getSimpleName();
     private static final float MAX_REVEAL = 0f;
     private static final float MIN_REVEAL = 1f;
+    private static final boolean DEBUG = true;
 
     private final ValueAnimator mAnimator;
     private final RevealStateListener mRevealListener;
@@ -57,6 +59,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (!mIsCanceled && mRevealListener != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "transition end");
+                    }
                     mRevealListener.onRevealEnd();
                 }
                 mIsCanceled = false;
@@ -65,6 +70,9 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 if (mRevealListener != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "transition start");
+                    }
                     mRevealListener.onRevealStart(true /* animate */);
                 }
             }
@@ -82,6 +90,9 @@
     }
 
     void updateAwake(boolean awake, long duration) {
+        if (DEBUG) {
+            Log.d(TAG, "updateAwake: awake=" + awake + ", duration=" + duration);
+        }
         mAwake = awake;
         mAnimator.setDuration(duration);
         if (duration == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 7b22a49..a8371e3 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -46,6 +46,7 @@
     private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
     private static final float SCALE_VIEWPORT_MIN = 1f;
     private static final float SCALE_VIEWPORT_MAX = 1.1f;
+    private static final boolean DEBUG = true;
 
     private final WallpaperManager mWallpaperManager;
     private final ImageGLProgram mProgram;
@@ -107,13 +108,24 @@
     }
 
     private boolean loadBitmap() {
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
+        }
         if (mWallpaperManager != null && mBitmap == null) {
             mBitmap = mWallpaperManager.getBitmap();
             mWallpaperManager.forgetLoadedWallpaper();
             if (mBitmap != null) {
-                mSurfaceSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+                float scale = (float) mScissor.height() / mBitmap.getHeight();
+                int surfaceHeight = Math.max(mScissor.height(), mBitmap.getHeight());
+                int surfaceWidth = scale > 1f
+                        ? Math.round(mBitmap.getWidth() * scale)
+                        : mBitmap.getWidth();
+                mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap done");
+        }
         return mBitmap != null;
     }
 
@@ -218,6 +230,7 @@
         out.print(prefix); out.print("mXOffset="); out.print(mXOffset);
         out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
         out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
+        out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
         mWallpaper.dump(prefix, fd, out, args);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 48f32cf..1d2de60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -72,7 +74,8 @@
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
-        NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
+        NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
+        SystemUIAppComponentFactory.ContextInitializer {
 
     private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
     public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
@@ -93,6 +96,7 @@
     @VisibleForTesting
     static final int ALARM_VISIBILITY_HOURS = 12;
 
+    private static final Object sInstanceLock = new Object();
     private static KeyguardSliceProvider sInstance;
 
     protected final Uri mSliceUri;
@@ -130,6 +134,7 @@
     protected boolean mDozing;
     private int mStatusBarState;
     private boolean mMediaIsVisible;
+    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -310,7 +315,10 @@
 
     @Override
     public boolean onCreateSliceProvider() {
-        synchronized (this) {
+        if (mContextAvailableCallback != null) {
+            mContextAvailableCallback.onContextAvailable(getContext());
+        }
+        synchronized (KeyguardSliceProvider.sInstanceLock) {
             KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
             if (oldInstance != null) {
                 oldInstance.onDestroy();
@@ -335,7 +343,7 @@
 
     @VisibleForTesting
     protected void onDestroy() {
-        synchronized (this) {
+        synchronized (KeyguardSliceProvider.sInstanceLock) {
             mNextAlarmController.removeCallback(this);
             mZenModeController.removeCallback(this);
             mMediaWakeLock.setAcquired(false);
@@ -345,6 +353,7 @@
                 getKeyguardUpdateMonitor().removeCallback(mKeyguardUpdateMonitorCallback);
                 getContext().unregisterReceiver(mIntentReceiver);
             }
+            KeyguardSliceProvider.sInstance = null;
         }
     }
 
@@ -449,9 +458,8 @@
         updateNextAlarm();
     }
 
-    @VisibleForTesting
-    protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
-        return KeyguardUpdateMonitor.getInstance(getContext());
+    private KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+        return Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     /**
@@ -530,4 +538,10 @@
             notifyChange();
         }
     }
+
+    @Override
+    public void setContextAvailableCallback(
+            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+        mContextAvailableCallback = callback;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4016b59..d2a9c75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -82,6 +82,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
@@ -98,6 +99,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * Mediates requests related to the keyguard.  This includes queries about the
  * state of the keyguard, power management events that effect whether the keyguard
@@ -215,6 +218,7 @@
     private boolean mBootSendUserPresent;
     private boolean mShuttingDown;
     private boolean mDozing;
+    private final FalsingManager mFalsingManager;
 
     /** High level access to the power manager for WakeLocks */
     private PowerManager mPM;
@@ -677,6 +681,13 @@
         }
     };
 
+    @Inject
+    public KeyguardViewMediator(FalsingManager falsingManager) {
+        super();
+
+        mFalsingManager = falsingManager;
+    }
+
     public void userActivity() {
         mPM.userActivity(SystemClock.uptimeMillis(), false);
     }
@@ -710,15 +721,14 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         mLockPatternUtils = new LockPatternUtils(mContext);
         KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
-        if (mContext.getResources().getBoolean(
-                com.android.keyguard.R.bool.config_enableKeyguardService)) {
+        if (mContext.getResources().getBoolean(R.bool.config_enableKeyguardService)) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
                             KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
@@ -846,7 +856,7 @@
                 playSounds(true);
             }
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedGoingToSleep(why);
+        mUpdateMonitor.dispatchStartedGoingToSleep(why);
         notifyStartedGoingToSleep();
     }
 
@@ -891,7 +901,7 @@
             }
 
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
+        mUpdateMonitor.dispatchFinishedGoingToSleep(why);
     }
 
     private long getLockTimeout(int userId) {
@@ -1004,7 +1014,7 @@
             if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedWakingUp();
+        mUpdateMonitor.dispatchStartedWakingUp();
         maybeSendUserPresentBroadcast();
         Trace.endSection();
     }
@@ -1047,7 +1057,7 @@
      * if there is a secure lock pattern.
      */
     public void onDreamingStarted() {
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStarted();
+        mUpdateMonitor.dispatchDreamingStarted();
         synchronized (this) {
             if (mDeviceInteractive
                     && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
@@ -1060,7 +1070,7 @@
      * A dream stopped.
      */
     public void onDreamingStopped() {
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStopped();
+        mUpdateMonitor.dispatchDreamingStopped();
         synchronized (this) {
             if (mDeviceInteractive) {
                 cancelDoKeyguardLaterLocked();
@@ -1456,11 +1466,11 @@
 
     public boolean isSecure(int userId) {
         return mLockPatternUtils.isSecure(userId)
-                || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure();
+                || mUpdateMonitor.isSimPinSecure();
     }
 
     public void setSwitchingUser(boolean switching) {
-        KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(switching);
+        mUpdateMonitor.setSwitchingUser(switching);
     }
 
     /**
@@ -1603,7 +1613,7 @@
                     Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
-                    Dependency.get(FalsingManager.class).onSucccessfulUnlock();
+                    mFalsingManager.onSucccessfulUnlock();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
@@ -2075,11 +2085,10 @@
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
             ViewGroup container, NotificationPanelView panelView,
             BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
-            View notificationContainer, KeyguardBypassController bypassController,
-            FalsingManager falsingManager) {
+            View notificationContainer, KeyguardBypassController bypassController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
                 biometricUnlockController, mDismissCallbackRegistry, lockIconContainer,
-                notificationContainer, bypassController, falsingManager);
+                notificationContainer, bypassController, mFalsingManager);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
new file mode 100644
index 0000000..6795bff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -0,0 +1,381 @@
+/*
+ * 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.pip;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.policy.PipSnapAlgorithm;
+
+import java.io.PrintWriter;
+
+/**
+ * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
+ * state changes originated from Window Manager and is the source of truth for PiP window bounds.
+ */
+public class PipBoundsHandler {
+
+    private static final String TAG = PipBoundsHandler.class.getSimpleName();
+    private static final float INVALID_SNAP_FRACTION = -1f;
+
+    private final Context mContext;
+    private final IWindowManager mWindowManager;
+    private final PipSnapAlgorithm mSnapAlgorithm;
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final Rect mStableInsets = new Rect();
+    private final Rect mTmpInsets = new Rect();
+    private final Point mTmpDisplaySize = new Point();
+
+    private IPinnedStackController mPinnedStackController;
+    private ComponentName mLastPipComponentName;
+    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
+
+    private float mDefaultAspectRatio;
+    private float mMinAspectRatio;
+    private float mMaxAspectRatio;
+    private float mAspectRatio;
+    private int mDefaultStackGravity;
+    private int mDefaultMinSize;
+    private Point mScreenEdgeInsets;
+    private int mCurrentMinSize;
+
+    private boolean mIsMinimized;
+    private boolean mIsImeShowing;
+    private int mImeHeight;
+    private boolean mIsShelfShowing;
+    private int mShelfHeight;
+
+    public PipBoundsHandler(Context context) {
+        mContext = context;
+        mSnapAlgorithm = new PipSnapAlgorithm(context);
+        mWindowManager = WindowManagerGlobal.getWindowManagerService();
+        reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
+    }
+
+    /**
+     * TODO: move the resources to SysUI package.
+     */
+    private void reloadResources() {
+        final Resources res = mContext.getResources();
+        mDefaultAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
+        mDefaultStackGravity = res.getInteger(
+                com.android.internal.R.integer.config_defaultPictureInPictureGravity);
+        mDefaultMinSize = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+        mCurrentMinSize = mDefaultMinSize;
+        final String screenEdgeInsetsDpString = res.getString(
+                com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+        mMinAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+        mMaxAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
+    }
+
+    public void setPinnedStackController(IPinnedStackController controller) {
+        mPinnedStackController = controller;
+    }
+
+    public void setMinEdgeSize(int minEdgeSize) {
+        mCurrentMinSize = minEdgeSize;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on IME visibility change.
+     */
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        mIsImeShowing = imeVisible;
+        mImeHeight = imeHeight;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on shelf visibility change.
+     */
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        mIsShelfShowing = shelfVisible;
+        mShelfHeight = shelfHeight;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on minimized state change.
+     */
+    public void onMinimizedStateChanged(boolean minimized) {
+        mIsMinimized = minimized;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on movement bounds change.
+     * Note that both inset and normal bounds will be calculated here rather than in the caller.
+     */
+    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+            Rect animatingBounds, DisplayInfo displayInfo) {
+        getInsetBounds(insetBounds);
+        final Rect defaultBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
+        normalBounds.set(defaultBounds);
+        if (animatingBounds.isEmpty()) {
+            animatingBounds.set(defaultBounds);
+        }
+        if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+            transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                    false /* useCurrentMinEdgeSize */);
+        }
+        displayInfo.copyFrom(mDisplayInfo);
+    }
+
+    /**
+     * Responds to IPinnedStackListener on saving reentry snap fraction
+     * for a given {@link ComponentName}.
+     */
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        mReentrySnapFraction = getSnapFraction(bounds);
+        mLastPipComponentName = componentName;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on resetting reentry snap fraction
+     * for a given {@link ComponentName}.
+     */
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        if (componentName.equals(mLastPipComponentName)) {
+            onResetReentrySnapFractionUnchecked();
+        }
+    }
+
+    private void onResetReentrySnapFractionUnchecked() {
+        mReentrySnapFraction = INVALID_SNAP_FRACTION;
+        mLastPipComponentName = null;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on {@link DisplayInfo} change.
+     * It will normally follow up with a
+     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
+     */
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+    }
+
+    /**
+     * Responds to IPinnedStackListener on configuration change.
+     */
+    public void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     * It will normally follow up with a
+     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
+     */
+    public void onAspectRatioChanged(float aspectRatio) {
+        mAspectRatio = aspectRatio;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on preparing the pinned stack animation.
+     */
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        final Rect destinationBounds;
+        if (bounds == null) {
+            destinationBounds = getDefaultBounds(mReentrySnapFraction);
+        } else {
+            destinationBounds = new Rect(bounds);
+        }
+        if (isValidPictureInPictureAspectRatio(aspectRatio)) {
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio,
+                    false /* useCurrentMinEdgeSize */);
+        }
+        if (destinationBounds.equals(bounds)) {
+            return;
+        }
+        mAspectRatio = aspectRatio;
+        onResetReentrySnapFractionUnchecked();
+        try {
+            mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
+                    -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start PiP animation from SysUI", e);
+        }
+    }
+
+    /**
+     * @return whether the given {@param aspectRatio} is valid.
+     */
+    private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+        return Float.compare(mMinAspectRatio, aspectRatio) <= 0
+                && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
+    }
+
+    /**
+     * Set the current bounds (or the default bounds if there are no current bounds) with the
+     * specified aspect ratio.
+     */
+    private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
+            boolean useCurrentMinEdgeSize) {
+        // Save the snap fraction, calculate the aspect ratio based on screen size
+        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
+                getMovementBounds(stackBounds));
+
+        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
+        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
+                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
+        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
+        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+        if (mIsMinimized) {
+            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
+        }
+    }
+
+    /**
+     * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
+     * will apply the default bounds to the provided snap fraction.
+     */
+    private Rect getDefaultBounds(float snapFraction) {
+        final Rect insetBounds = new Rect();
+        getInsetBounds(insetBounds);
+
+        final Rect defaultBounds = new Rect();
+        final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
+                mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        if (snapFraction != INVALID_SNAP_FRACTION) {
+            defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
+            final Rect movementBounds = getMovementBounds(defaultBounds);
+            mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
+        } else {
+            Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
+                    0, Math.max(mIsImeShowing ? mImeHeight : 0,
+                            mIsShelfShowing ? mShelfHeight : 0),
+                    defaultBounds);
+        }
+        return defaultBounds;
+    }
+
+    /**
+     * Populates the bounds on the screen that the PIP can be visible in.
+     */
+    private void getInsetBounds(Rect outRect) {
+        try {
+            mWindowManager.getStableInsets(mContext.getDisplayId(), mTmpInsets);
+            outRect.set(mTmpInsets.left + mScreenEdgeInsets.x,
+                    mTmpInsets.top + mScreenEdgeInsets.y,
+                    mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
+                    mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets from WM", e);
+        }
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds) {
+        return getMovementBounds(stackBounds, true /* adjustForIme */);
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
+        final Rect movementBounds = new Rect();
+        getInsetBounds(movementBounds);
+
+        // Apply the movement bounds adjustments based on the current state.
+        mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+                (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
+        return movementBounds;
+    }
+
+    /**
+     * Applies the minimized offsets to the given stack bounds.
+     */
+    private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
+        mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        try {
+            mWindowManager.getStableInsets(mContext.getDisplayId(), mStableInsets);
+            mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
+                    mStableInsets);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets from WM", e);
+        }
+    }
+
+    /**
+     * @return the default snap fraction to apply instead of the default gravity when calculating
+     *         the default stack bounds when first entering PiP.
+     */
+    private float getSnapFraction(Rect stackBounds) {
+        return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
+    }
+
+    /**
+     * @return the pixels for a given dp value.
+     */
+    private int dpToPx(float dpValue, DisplayMetrics dm) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+    }
+
+    /**
+     * Dumps internal states.
+     */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
+        pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
+        pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
+        pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
+        pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
+        pw.println(innerPrefix + "mMaxAspectRatio=" + mMaxAspectRatio);
+        pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
+        pw.println(innerPrefix + "mDefaultStackGravity=" + mDefaultStackGravity);
+        pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized);
+        pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+        pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+        pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+        pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+        mSnapAlgorithm.dump(pw, innerPrefix);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 1740290..8dfae32 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
@@ -29,14 +32,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -55,8 +60,12 @@
     private IActivityTaskManager mActivityTaskManager;
     private Handler mHandler = new Handler();
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
 
+    private PipBoundsHandler mPipBoundsHandler;
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
@@ -116,11 +125,11 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onListenerRegistered(IPinnedStackController controller) {
             mHandler.post(() -> {
+                mPipBoundsHandler.setPinnedStackController(controller);
                 mTouchHandler.setPinnedStackController(controller);
             });
         }
@@ -128,6 +137,7 @@
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
                 mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
@@ -135,31 +145,66 @@
         @Override
         public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
             mHandler.post(() -> {
-               mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mPipBoundsHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
             });
         }
 
         @Override
         public void onMinimizedStateChanged(boolean isMinimized) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onMinimizedStateChanged(isMinimized);
                 mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
             });
         }
 
         @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
-                        fromImeAdjustment, fromShelfAdjustment, displayRotation);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+                        mTmpDisplayInfo.rotation);
             });
         }
 
         @Override
         public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> mMenuController.setAppActions(actions));
+        }
+
+        @Override
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+            mHandler.post(() -> mPipBoundsHandler.onSaveReentrySnapFraction(componentName, bounds));
+        }
+
+        @Override
+        public void onResetReentrySnapFraction(ComponentName componentName) {
+            mHandler.post(() -> mPipBoundsHandler.onResetReentrySnapFraction(componentName));
+        }
+
+        @Override
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
+        }
+
+        @Override
+        public void onConfigurationChanged() {
+            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
+        }
+
+        @Override
+        public void onAspectRatioChanged(float aspectRatio) {
+            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+        }
+
+        @Override
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
             mHandler.post(() -> {
-                mMenuController.setAppActions(actions);
+                mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
             });
         }
     }
@@ -181,15 +226,27 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
-        mInputConsumerController.registerInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager);
         mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager,
-                mMenuController, mInputConsumerController);
+                mMenuController, mInputConsumerController, mPipBoundsHandler);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
+
+        // If SystemUI restart, and it already existed a pinned stack,
+        // register the pip input consumer to ensure touch can send to it.
+        try {
+            ActivityManager.StackInfo stackInfo = mActivityTaskManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+            if (stackInfo != null) {
+                mInputConsumerController.registerInputConsumer();
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
     }
 
     /**
@@ -238,5 +295,6 @@
         mInputConsumerController.dump(pw, innerPrefix);
         mMenuController.dump(pw, innerPrefix);
         mTouchHandler.dump(pw, innerPrefix);
+        mPipBoundsHandler.dump(pw, innerPrefix);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 21f5812..4f2a6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -48,7 +48,6 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -64,7 +63,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityManager;
@@ -92,6 +90,7 @@
     public static final int MESSAGE_UPDATE_ACTIONS = 4;
     public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
     public static final int MESSAGE_ANIMATION_ENDED = 6;
+    public static final int MESSAGE_TOUCH_EVENT = 7;
 
     private static final int INITIAL_DISMISS_DELAY = 3500;
     private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -128,10 +127,6 @@
                 }
             };
 
-    private PipTouchState mTouchState;
-    private PointF mDownPosition = new PointF();
-    private PointF mDownDelta = new PointF();
-    private ViewConfiguration mViewConfig;
     private Handler mHandler = new Handler();
     private Messenger mToControllerMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
@@ -169,6 +164,12 @@
                     mAllowTouches = true;
                     break;
                 }
+
+                case MESSAGE_TOUCH_EVENT: {
+                    final MotionEvent ev = (MotionEvent) msg.obj;
+                    dispatchTouchEvent(ev);
+                    break;
+                }
             }
         }
     });
@@ -184,15 +185,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         // Set the flags to allow us to watch for outside touches and also hide the menu and start
         // manipulating the PIP in the same touch gesture
-        mViewConfig = ViewConfiguration.get(this);
-        mTouchState = new PipTouchState(mViewConfig, mHandler, () -> {
-            if (mMenuState == MENU_STATE_CLOSE) {
-                showPipMenu();
-            } else {
-                expandPip();
-            }
-        });
-        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
+        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.pip_menu_activity);
@@ -204,32 +197,6 @@
         mViewRoot.setBackground(mBackgroundDrawable);
         mMenuContainer = findViewById(R.id.menu_container);
         mMenuContainer.setAlpha(0);
-        mMenuContainer.setOnTouchListener((v, event) -> {
-            mTouchState.onTouchEvent(event);
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_UP:
-                    if (mTouchState.isDoubleTap() || mMenuState == MENU_STATE_FULL) {
-                        // Expand to fullscreen if this is a double tap or we are already expanded
-                        expandPip();
-                    } else if (!mTouchState.isWaitingForDoubleTap()) {
-                        // User has stalled long enough for this not to be a drag or a double tap,
-                        // just expand the menu if necessary
-                        if (mMenuState == MENU_STATE_CLOSE) {
-                            showPipMenu();
-                        }
-                    } else {
-                        // Next touch event _may_ be the second tap for the double-tap, schedule a
-                        // fallback runnable to trigger the menu if no touch event occurs before the
-                        // next tap
-                        mTouchState.scheduleDoubleTapTimeoutCallback();
-                    }
-                    // Fall through
-                case MotionEvent.ACTION_CANCEL:
-                    mTouchState.reset();
-                    break;
-            }
-            return true;
-        });
         mSettingsButton = findViewById(R.id.settings);
         mSettingsButton.setAlpha(0);
         mSettingsButton.setOnClickListener((v) -> {
@@ -240,7 +207,11 @@
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setAlpha(0);
         mDismissButton.setOnClickListener(v -> dismissPip());
-        findViewById(R.id.expand_button).setOnClickListener(v -> expandPip());
+        findViewById(R.id.expand_button).setOnClickListener(v -> {
+            if (mMenuContainer.getAlpha() != 0) {
+                expandPip();
+            }
+        });
         mActionsGroup = findViewById(R.id.actions_group);
         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
                 R.dimen.pip_between_action_padding_land);
@@ -298,27 +269,14 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (!mAllowTouches) {
-            return super.dispatchTouchEvent(ev);
+            return false;
         }
 
         // On the first action outside the window, hide the menu
         switch (ev.getAction()) {
             case MotionEvent.ACTION_OUTSIDE:
                 hideMenu();
-                break;
-            case MotionEvent.ACTION_DOWN:
-                mDownPosition.set(ev.getX(), ev.getY());
-                mDownDelta.set(0f, 0f);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
-                if (mDownDelta.length() > mViewConfig.getScaledTouchSlop()
-                        && mMenuState != MENU_STATE_NONE) {
-                    // Restore the input consumer and let that drive the movement of this menu
-                    notifyRegisterInputConsumer();
-                    cancelDelayedFinish();
-                }
-                break;
+                return true;
         }
         return super.dispatchTouchEvent(ev);
     }
@@ -381,7 +339,6 @@
             if (allowMenuTimeout) {
                 repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
             }
-            notifyUnregisterInputConsumer();
         }
     }
 
@@ -506,11 +463,13 @@
                     actionView.setContentDescription(action.getContentDescription());
                     if (action.isEnabled()) {
                         actionView.setOnClickListener(v -> {
-                            try {
-                                action.getActionIntent().send();
-                            } catch (CanceledException e) {
-                                Log.w(TAG, "Failed to send action", e);
-                            }
+                            mHandler.post(() -> {
+                                try {
+                                    action.getActionIntent().send();
+                                } catch (CanceledException e) {
+                                    Log.w(TAG, "Failed to send action", e);
+                                }
+                            });
                         });
                     }
                     actionView.setEnabled(action.isEnabled());
@@ -554,18 +513,6 @@
         mBackgroundDrawable.setAlpha(alpha);
     }
 
-    private void notifyRegisterInputConsumer() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER;
-        sendMessage(m, "Could not notify controller to register input consumer");
-    }
-
-    private void notifyUnregisterInputConsumer() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_UNREGISTER_INPUT_CONSUMER;
-        sendMessage(m, "Could not notify controller to unregister input consumer");
-    }
-
     private void notifyMenuStateChange(int menuState) {
         mMenuState = menuState;
         Message m = Message.obtain();
@@ -583,11 +530,6 @@
         }, false /* notifyMenuVisibility */, false /* isDismissing */);
     }
 
-    private void minimizePip() {
-        sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
-                "Could not notify controller to minimize PIP");
-    }
-
     private void dismissPip() {
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
@@ -597,12 +539,6 @@
         }, false /* notifyMenuVisibility */, true /* isDismissing */);
     }
 
-    private void showPipMenu() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
-        sendMessage(m, "Could not notify controller to show PIP menu");
-    }
-
     private void showSettings() {
         final Pair<ComponentName, Integer> topPipActivityInfo =
                 PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 14459d6..62c59e5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.systemui.pip.phone.PipMediaController.ActionListener;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -156,14 +157,6 @@
                     mListeners.forEach(l -> l.onPipShowMenu());
                     break;
                 }
-                case MESSAGE_REGISTER_INPUT_CONSUMER: {
-                    mInputConsumerController.registerInputConsumer();
-                    break;
-                }
-                case MESSAGE_UNREGISTER_INPUT_CONSUMER: {
-                    mInputConsumerController.unregisterInputConsumer();
-                    break;
-                }
                 case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
                     mToActivityMessenger = msg.replyTo;
                     setStartActivityRequested(false);
@@ -212,15 +205,12 @@
     }
 
     public void onActivityPinned() {
-        if (mMenuState == MENU_STATE_NONE) {
-            // If the menu is not visible, then re-register the input consumer if it is not already
-            // registered
-            mInputConsumerController.registerInputConsumer();
-        }
+        mInputConsumerController.registerInputConsumer();
     }
 
     public void onActivityUnpinned() {
         hideMenu();
+        mInputConsumerController.unregisterInputConsumer();
         setStartActivityRequested(false);
     }
 
@@ -495,11 +485,7 @@
             Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
                     + " menuState=" + menuState + " resize=" + resize);
         }
-        if (menuState == MENU_STATE_NONE) {
-            mInputConsumerController.registerInputConsumer();
-        } else {
-            mInputConsumerController.unregisterInputConsumer();
-        }
+
         if (menuState != mMenuState) {
             mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize));
             if (menuState == MENU_STATE_FULL) {
@@ -521,6 +507,22 @@
         mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0;
     }
 
+    /**
+     * Handles touch event sent from pip input consumer.
+     */
+    void handleTouchEvent(MotionEvent ev) {
+        if (mToActivityMessenger != null) {
+            Message m = Message.obtain();
+            m.what = PipMenuActivity.MESSAGE_TOUCH_EVENT;
+            m.obj = ev;
+            try {
+                mToActivityMessenger.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not dispatch touch event", e);
+            }
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 9c65994..fa60477 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -551,9 +551,8 @@
                         return true;
                     }
 
-                    mActivityTaskManager.resizeStack(stackInfo.stackId, toBounds,
-                            false /* allowResizeInDockedMode */, true /* preserveWindows */,
-                            true /* animate */, duration);
+                    mActivityTaskManager.animateResizePinnedStack(stackInfo.stackId, toBounds,
+                            duration);
                     mBounds.set(toBounds);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b05058a..1f36d97 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,6 +48,7 @@
 import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.systemui.R;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
@@ -75,6 +76,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final ViewConfiguration mViewConfig;
     private final PipMenuListener mMenuListener = new PipMenuListener();
+    private final PipBoundsHandler mPipBoundsHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
@@ -178,7 +180,8 @@
 
     public PipTouchHandler(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
-            InputConsumerController inputConsumerController) {
+            InputConsumerController inputConsumerController,
+            PipBoundsHandler pipBoundsHandler) {
 
         // Initialize the Pip input consumer
         mContext = context;
@@ -211,6 +214,8 @@
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
         onRegistrationChanged(inputConsumerController.isRegistered());
+
+        mPipBoundsHandler = pipBoundsHandler;
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -363,6 +368,8 @@
         // Update the touch state
         mTouchState.onTouchEvent(ev);
 
+        boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 mMotionHelper.synchronizePinnedStackBounds();
@@ -378,6 +385,8 @@
                         break;
                     }
                 }
+
+                shouldDeliverToMenu = !mTouchState.isDragging();
                 break;
             }
             case MotionEvent.ACTION_UP: {
@@ -394,6 +403,7 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
+                shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
                 mTouchState.reset();
                 break;
             }
@@ -425,7 +435,20 @@
                 break;
             }
         }
-        return mMenuState == MENU_STATE_NONE;
+
+        // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+        if (shouldDeliverToMenu) {
+            final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+            // Send the cancel event and cancel menu timeout if it starts to drag.
+            if (mTouchState.startedDragging()) {
+                cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+                mMenuController.pokeMenu();
+            }
+
+            mMenuController.handleTouchEvent(cloneEvent);
+        }
+
+        return true;
     }
 
     /**
@@ -741,11 +764,11 @@
                 mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
                         null /* animatorListener */);
                 setMinimizedStateInternal(false);
+            } else if (mTouchState.isDoubleTap()) {
+                // Expand to fullscreen if this is a double tap
+                mMotionHelper.expandPip();
             } else if (mMenuState != MENU_STATE_FULL) {
-                if (mTouchState.isDoubleTap()) {
-                    // Expand to fullscreen if this is a double tap
-                    mMotionHelper.expandPip();
-                } else if (!mTouchState.isWaitingForDoubleTap()) {
+                if (!mTouchState.isWaitingForDoubleTap()) {
                     // User has stalled long enough for this not to be a drag or a double tap, just
                     // expand the menu
                     mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
@@ -756,9 +779,6 @@
                     // next tap
                     mTouchState.scheduleDoubleTapTimeoutCallback();
                 }
-            } else {
-                mMenuController.hideMenu();
-                mMotionHelper.expandPip();
             }
             return true;
         }
@@ -772,14 +792,8 @@
         mMovementBounds = isMenuExpanded
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
-        try {
-            if (mPinnedStackController != null) {
-                mPinnedStackController.setMinEdgeSize(
-                        isMenuExpanded ? mExpandedShortestEdgeSize : 0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set minimized state", e);
-        }
+        mPipBoundsHandler.setMinEdgeSize(
+                isMenuExpanded ? mExpandedShortestEdgeSize : 0);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 69efbc8..e3f65ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -106,6 +106,7 @@
                 mIsDoubleTap = !mPreviouslyDragging &&
                         (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
                 mIsWaitingForDoubleTap = false;
+                mIsDragging = false;
                 mLastDownTouchTime = mDownTouchTime;
                 if (mDoubleTapTimeoutCallback != null) {
                     mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 5f2614e..81d6973 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,13 +19,10 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,16 +43,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
+import android.view.DisplayInfo;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -111,9 +107,8 @@
     private int mSuspendPipResizingReason;
 
     private Context mContext;
-    private IActivityManager mActivityManager;
+    private PipBoundsHandler mPipBoundsHandler;
     private IActivityTaskManager mActivityTaskManager;
-    private IWindowManager mWindowManager;
     private MediaSessionManager mMediaSessionManager;
     private int mState = STATE_NO_PIP;
     private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
@@ -135,11 +130,16 @@
     private PipNotification mPipNotification;
     private ParceledListSlice mCustomActions;
 
+    // Used to calculate the movement bounds
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
+
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
     private int mImeHeightAdjustment;
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
 
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
@@ -181,11 +181,7 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {}
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mState == STATE_PIP) {
@@ -205,17 +201,13 @@
         }
 
         @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {}
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mDefaultPipBounds.set(normalBounds);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mDefaultPipBounds.set(animatingBounds);
             });
         }
 
@@ -241,10 +233,8 @@
         }
         mInitialized = true;
         mContext = context;
-
-        mActivityManager = ActivityManager.getService();
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mActivityTaskManager = ActivityTaskManager.getService();
-        mWindowManager = WindowManagerGlobal.getWindowManagerService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -291,7 +281,7 @@
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
 
         try {
-            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+            WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
@@ -441,8 +431,8 @@
         }
         try {
             int animationDurationMs = -1;
-            mActivityTaskManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
-                    true, true, true, animationDurationMs);
+            mActivityTaskManager.animateResizePinnedStack(mPinnedStackId, mCurrentPipBounds,
+                    animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 41f66f7..9221b68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -155,7 +155,7 @@
         } else if (MOVE_FULL_ROWS.equals(key)) {
             mFullRows = TunerService.parseIntegerSwitch(newValue, true);
         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
-            mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQs.getContext());
+            mNumQuickTiles = QuickQSPanel.parseNumTiles(newValue);
             clearAnimationState();
         }
         updateAnimators();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
index 8c21dde..55ae61d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -16,17 +16,20 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Dependency.BG_HANDLER;
+import static com.android.systemui.Dependency.BG_HANDLER_NAME;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -52,9 +55,11 @@
      */
     private static final int SIM_SLOTS = 3;
     private final NetworkController mNetworkController;
+    private final Handler mBgHandler;
 
     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
     private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+    private TextView mNoSimTextView;
     private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS];
     private CarrierTextController mCarrierTextController;
     private ActivityStarter mActivityStarter;
@@ -63,17 +68,20 @@
 
     @Inject
     public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            NetworkController networkController, ActivityStarter activityStarter) {
+            NetworkController networkController, ActivityStarter activityStarter,
+            @Named(BG_HANDLER_NAME) Handler handler) {
         super(context, attrs);
         mNetworkController = networkController;
         mActivityStarter = activityStarter;
+        mBgHandler = handler;
     }
 
     @VisibleForTesting
     public QSCarrierGroup(Context context, AttributeSet attrs) {
         this(context, attrs,
                 Dependency.get(NetworkController.class),
-                Dependency.get(ActivityStarter.class));
+                Dependency.get(ActivityStarter.class),
+                Dependency.get(BG_HANDLER));
     }
 
     @Override
@@ -94,10 +102,13 @@
         mCarrierDividers[0] = findViewById(R.id.qs_carrier_divider1);
         mCarrierDividers[1] = findViewById(R.id.qs_carrier_divider2);
 
+        mNoSimTextView = findViewById(R.id.no_carrier_text);
+
         for (int i = 0; i < SIM_SLOTS; i++) {
             mInfos[i] = new CellSignalState();
             mCarrierGroups[i].setOnClickListener(this);
         }
+        mNoSimTextView.setOnClickListener(this);
 
         CharSequence separator = mContext.getString(
                 com.android.internal.R.string.kg_text_message_separator);
@@ -110,8 +121,7 @@
             return;
         }
         mListening = listening;
-        // TODO(b/140053526)
-        whitelistIpcs(this::updateListeners);
+        mBgHandler.post(this::updateListeners);
     }
 
     @Override
@@ -155,50 +165,47 @@
 
     @Override
     public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-        if (info.airplaneMode) {
-            setVisibility(View.GONE);
-        } else {
-            setVisibility(View.VISIBLE);
-            if (info.anySimReady) {
-                boolean[] slotSeen = new boolean[SIM_SLOTS];
-                if (info.listOfCarriers.length == info.subscriptionIds.length) {
-                    for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
-                        int slot = getSlotIndex(info.subscriptionIds[i]);
-                        if (slot >= SIM_SLOTS) {
-                            Log.w(TAG, "updateInfoCarrier - slot: " + slot);
-                            continue;
-                        }
-                        if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                            Log.e(TAG,
-                                    "Invalid SIM slot index for subscription: "
-                                            + info.subscriptionIds[i]);
-                            continue;
-                        }
-                        mInfos[slot].visible = true;
-                        slotSeen[slot] = true;
-                        mCarrierGroups[slot].setCarrierText(
-                                info.listOfCarriers[i].toString().trim());
-                        mCarrierGroups[slot].setVisibility(View.VISIBLE);
+        mNoSimTextView.setVisibility(View.GONE);
+        if (!info.airplaneMode && info.anySimReady) {
+            boolean[] slotSeen = new boolean[SIM_SLOTS];
+            if (info.listOfCarriers.length == info.subscriptionIds.length) {
+                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                    int slot = getSlotIndex(info.subscriptionIds[i]);
+                    if (slot >= SIM_SLOTS) {
+                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
+                        continue;
                     }
-                    for (int i = 0; i < SIM_SLOTS; i++) {
-                        if (!slotSeen[i]) {
-                            mInfos[i].visible = false;
-                            mCarrierGroups[i].setVisibility(View.GONE);
-                        }
+                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG,
+                                "Invalid SIM slot index for subscription: "
+                                        + info.subscriptionIds[i]);
+                        continue;
                     }
-                } else {
-                    Log.e(TAG, "Carrier information arrays not of same length");
+                    mInfos[slot].visible = true;
+                    slotSeen[slot] = true;
+                    mCarrierGroups[slot].setCarrierText(
+                            info.listOfCarriers[i].toString().trim());
+                    mCarrierGroups[slot].setVisibility(View.VISIBLE);
+                }
+                for (int i = 0; i < SIM_SLOTS; i++) {
+                    if (!slotSeen[i]) {
+                        mInfos[i].visible = false;
+                        mCarrierGroups[i].setVisibility(View.GONE);
+                    }
                 }
             } else {
-                mInfos[0].visible = false;
-                mCarrierGroups[0].setCarrierText(info.carrierText);
-                mCarrierGroups[0].setVisibility(View.VISIBLE);
-                for (int i = 1; i < SIM_SLOTS; i++) {
-                    mInfos[i].visible = false;
-                    mCarrierGroups[i].setCarrierText("");
-                    mCarrierGroups[i].setVisibility(View.GONE);
-                }
+                Log.e(TAG, "Carrier information arrays not of same length");
             }
+        } else {
+            // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
+            // info.carrierText in a different view.
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                mInfos[i].visible = false;
+                mCarrierGroups[i].setCarrierText("");
+                mCarrierGroups[i].setVisibility(View.GONE);
+            }
+            mNoSimTextView.setText(info.carrierText);
+            mNoSimTextView.setVisibility(View.VISIBLE);
         }
         handleUpdateState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
new file mode 100644
index 0000000..3f0c5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.qs
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.plugins.qs.QSTile
+
+private const val TAG = "QSColorController"
+private const val QS_COLOR_ICON = "qs_color_icon"
+private const val QS_COLOR_ENABLED = "qs_color_enabled"
+private const val QS_COLOR_OVERRIDDEN_TILES = "qs_color_overridden_tiles"
+private val qsColorIconUri = Settings.System.getUriFor(QS_COLOR_ICON)
+private val qsColorEnabledUri = Settings.System.getUriFor(QS_COLOR_ENABLED)
+class QSColorController private constructor() {
+
+    private var overrideColor = false
+    private var colorIcon = false
+    private lateinit var colorCache: SettingBackedMap
+
+    companion object {
+        val instance = QSColorController()
+        internal fun overrideColor() = instance.overrideColor
+        internal fun colorIcon() = instance.colorIcon
+
+        @QSTile.ColorTile
+        private fun getColorFromSetting(setting: String): Int {
+            return when (setting.toLowerCase()) {
+                "red" -> QSTile.COLOR_TILE_RED
+                "blue" -> QSTile.COLOR_TILE_BLUE
+                "green" -> QSTile.COLOR_TILE_GREEN
+                "yellow" -> QSTile.COLOR_TILE_YELLOW
+                else -> QSTile.COLOR_TILE_ACCENT
+            }
+        }
+    }
+
+    private fun getBooleanSetting(key: String, default: Boolean = false): Boolean =
+            try {
+                Settings.System.getInt(contentResolver, key) != 0
+            } catch (_: Settings.SettingNotFoundException) {
+                default
+            }
+
+    private lateinit var tileHost: QSHost
+    private lateinit var contentResolver: ContentResolver
+
+    fun initQSTileHost(host: QSHost) {
+        tileHost = host
+        contentResolver = tileHost.context.contentResolver
+        colorCache = SettingBackedMap(contentResolver, mutableMapOf())
+        colorIcon = getBooleanSetting(QS_COLOR_ICON)
+        overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
+        readExistingSettings()
+        contentResolver.registerContentObserver(qsColorEnabledUri, true, settingsListener)
+        contentResolver.registerContentObserver(qsColorIconUri, false, settingsListener)
+    }
+
+    private fun readExistingSettings() {
+        Settings.System.getString(contentResolver, QS_COLOR_OVERRIDDEN_TILES)?.split(",")
+                ?.mapNotNull { spec ->
+            Settings.System.getString(contentResolver, "$QS_COLOR_ENABLED/$spec")?.let {
+                spec to it
+            }
+        }?.forEach {
+            modifyTileColor(it.first, getColorFromSetting(it.second))
+        }
+    }
+
+    private val settingsListener = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean, uri: Uri) {
+            super.onChange(selfChange, uri)
+            when (uri) {
+                qsColorIconUri -> colorIcon = getBooleanSetting(QS_COLOR_ICON)
+                qsColorEnabledUri -> overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
+                else -> {
+                    uri.path?.drop("/system/".length)?.let {
+                        val color = getColorFromSetting(
+                                Settings.System.getString(contentResolver, it) ?: "accent")
+                        val tileSpec = uri.lastPathSegment ?: ""
+                        modifyTileColor(tileSpec, color)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun modifyTileColor(spec: String, @QSTile.ColorTile color: Int) {
+        Log.w(TAG, "Setting color of tile $spec to $color")
+        colorCache.put(spec, color)
+        tileHost.tiles.firstOrNull { it.tileSpec == spec }?.setColor(color)
+    }
+
+    fun applyColorToTile(tile: QSTile) {
+        colorCache.get(tile.tileSpec)?.let {
+            modifyTileColor(tile.tileSpec, it)
+        }
+    }
+
+    fun applyColorToAllTiles() = tileHost.tiles.forEach(::applyColorToTile)
+
+    fun destroy() {
+        contentResolver.unregisterContentObserver(settingsListener)
+    }
+
+    class SettingBackedMap(
+        private val contentResolver: ContentResolver,
+        private val map: MutableMap<String, Int>
+    ) : MutableMap<String, @QSTile.ColorTile Int> by map {
+        override fun put(key: String, @QSTile.ColorTile value: Int): Int? {
+            return map.put(key, value).also {
+                Settings.System.putString(contentResolver, QS_COLOR_OVERRIDDEN_TILES,
+                        map.filterValues { it != QSTile.COLOR_TILE_ACCENT }
+                                .keys
+                                .joinToString(","))
+            }
+        }
+    }
+}
+fun overrideColor() = QSColorController.overrideColor()
+fun colorIcon() = QSColorController.colorIcon()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index b597a72..0a2533a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -29,6 +29,11 @@
     void setQSPanel(@Nullable QSPanel panel);
 
     /**
+     * Sets the given {@link QuickQSPanel} to be the one associated with quick settings.
+     */
+    default void setQQSPanel(@Nullable QuickQSPanel panel) {};
+
+    /**
      * Sets whether or not the footer should be visible.
      *
      * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE} or {@link View#GONE}.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 644664e..0134aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -81,6 +81,7 @@
 
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
+    private QuickQSPanel mQuickQsPanel;
 
     private boolean mExpanded;
 
@@ -177,7 +178,8 @@
     }
 
     private void updateAnimator(int width) {
-        int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
+        int numTiles = mQuickQsPanel != null ? mQuickQsPanel.getNumQuickTiles()
+                : QuickQSPanel.getDefaultMaxTiles();
         int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
                 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
         int remaining = (width - numTiles * size) / (numTiles - 1);
@@ -346,6 +348,11 @@
     }
 
     @Override
+    public void setQQSPanel(@Nullable QuickQSPanel panel) {
+        mQuickQsPanel = panel;
+    }
+
+    @Override
     public void onClick(View v) {
         // Don't do anything until view are unhidden
         if (!mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index b27bf32..1e763cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -88,6 +88,8 @@
     private int mCurrentUser;
     private StatusBar mStatusBar;
 
+    private QSColorController mQSColorController = QSColorController.Companion.getInstance();
+
     @Inject
     public QSTileHost(Context context,
             StatusBarIconController iconController,
@@ -119,6 +121,8 @@
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
             mAutoTiles = autoTiles.get();
         });
+
+        mQSColorController.initQSTileHost(this);
     }
 
     public StatusBarIconController getIconController() {
@@ -132,6 +136,8 @@
         mServices.destroy();
         mPluginManager.removePluginListener(this);
         mDumpController.unregisterDumpable(this);
+
+        mQSColorController.destroy();
     }
 
     @Override
@@ -216,7 +222,7 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
-        if (DEBUG) Log.d(TAG, "Recreating tiles");
+        Log.d(TAG, "Recreating tiles");
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
@@ -225,7 +231,7 @@
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
                 tile -> {
-                    if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+                    Log.d(TAG, "Destroying tile: " + tile.getKey());
                     tile.getValue().destroy();
                 });
         final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
@@ -242,9 +248,10 @@
                     newTiles.put(tileSpec, tile);
                 } else {
                     tile.destroy();
+                    Log.d(TAG, "Destroying not available tile: " + tileSpec);
                 }
             } else {
-                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
+                Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
                     tile = createTile(tileSpec);
                     if (tile != null) {
@@ -253,6 +260,7 @@
                             newTiles.put(tileSpec, tile);
                         } else {
                             tile.destroy();
+                            Log.d(TAG, "Destroying not available tile: " + tileSpec);
                         }
                     }
                 } catch (Throwable t) {
@@ -268,13 +276,15 @@
         mTiles.putAll(newTiles);
         if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
             // If we didn't manage to create any tiles, set it to empty (default)
-            if (DEBUG) Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
+            Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
             changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
         } else {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 mCallbacks.get(i).onTilesChanged();
             }
         }
+
+        mQSColorController.applyColorToAllTiles();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 37113cf..85aafa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
@@ -50,9 +49,10 @@
 
     public static final String NUM_QUICK_TILES = "sysui_qqs_count";
     private static final String TAG = "QuickQSPanel";
+    // Start it at 6 so a non-zero value can be obtained statically.
+    private static int sDefaultMaxTiles = 6;
 
     private boolean mDisabledByPolicy;
-    private static int mDefaultMaxTiles;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
 
@@ -69,7 +69,7 @@
             }
             removeView((View) mTileLayout);
         }
-        mDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+        sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
         mTileLayout = new HeaderTileLayout(context);
         mTileLayout.setListening(mListening);
         addView((View) mTileLayout, 0 /* Between brightness and footer */);
@@ -107,11 +107,6 @@
     }
 
     @Override
-    protected boolean shouldShowDetail() {
-        return !mExpanded;
-    }
-
-    @Override
     protected void drawTile(TileRecord r, State state) {
         if (state instanceof SignalState) {
             SignalState copy = new SignalState();
@@ -160,14 +155,31 @@
     private final Tunable mNumTiles = new Tunable() {
         @Override
         public void onTuningChanged(String key, String newValue) {
-            setMaxTiles(getNumQuickTiles(mContext));
+            setMaxTiles(parseNumTiles(newValue));
         }
     };
 
-    public static int getNumQuickTiles(Context context) {
-        // TODO(b/140052679)
-        return whitelistIpcs(() ->
-                Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, mDefaultMaxTiles));
+    public int getNumQuickTiles() {
+        return mMaxTiles;
+    }
+
+    /**
+     * Parses the String setting into the number of tiles. Defaults to {@code mDefaultMaxTiles}
+     *
+     * @param numTilesValue value of the setting to parse
+     * @return parsed value of numTilesValue OR {@code mDefaultMaxTiles} on error
+     */
+    public static int parseNumTiles(String numTilesValue) {
+        try {
+            return Integer.parseInt(numTilesValue);
+        } catch (NumberFormatException e) {
+            // Couldn't read an int from the new setting value. Use default.
+            return sDefaultMaxTiles;
+        }
+    }
+
+    public static int getDefaultMaxTiles() {
+        return sDefaultMaxTiles;
     }
 
     void setDisabledByPolicy(boolean disabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 83b000d..7fb5207 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -38,20 +38,22 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Allows full-screen customization of QS, through show() and hide().
  *
@@ -66,6 +68,8 @@
 
     private final QSDetailClipper mClipper;
     private final LightBarController mLightBarController;
+    private KeyguardStateController mKeyguardStateController;
+    private final ScreenLifecycle mScreenLifecycle;
     private final TileQueryHelper mTileQueryHelper;
     private final View mTransparentView;
 
@@ -82,7 +86,11 @@
     private boolean mOpening;
     private boolean mIsShowingNavBackdrop;
 
-    public QSCustomizer(Context context, AttributeSet attrs) {
+    @Inject
+    public QSCustomizer(Context context, AttributeSet attrs,
+            LightBarController lightBarController,
+            KeyguardStateController keyguardStateController,
+            ScreenLifecycle screenLifecycle) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
 
         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
@@ -115,7 +123,9 @@
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
-        mLightBarController = Dependency.get(LightBarController.class);
+        mLightBarController = lightBarController;
+        mKeyguardStateController = keyguardStateController;
+        mScreenLifecycle = screenLifecycle;
         updateNavBackDrop(getResources().getConfiguration());
     }
 
@@ -177,7 +187,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(true);
             mNotifQsContainer.setCustomizerShowing(true);
-            Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            mKeyguardStateController.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -193,7 +203,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(false);
             mNotifQsContainer.setCustomizerShowing(true);
-            Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            mKeyguardStateController.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -203,16 +213,21 @@
     }
 
     public void hide() {
+        final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
         if (isShown) {
             MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
             isShown = false;
             mToolbar.dismissPopupMenus();
             setCustomizing(false);
             save();
-            mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
-            mNotifQsContainer.setCustomizerAnimating(true);
+            if (animate) {
+                mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
+            } else {
+                setVisibility(View.GONE);
+            }
+            mNotifQsContainer.setCustomizerAnimating(animate);
             mNotifQsContainer.setCustomizerShowing(false);
-            Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -268,7 +283,7 @@
 
     public void saveInstanceState(Bundle outState) {
         if (isShown) {
-            Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
         }
         outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
     }
@@ -300,7 +315,7 @@
         @Override
         public void onKeyguardShowingChanged() {
             if (!isAttachedToWindow()) return;
-            if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) {
+            if (mKeyguardStateController.isShowing() && !mOpening) {
                 hide();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index b135f7b..effea6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -159,8 +159,11 @@
             mBindTryCount++;
             try {
                 mIsBound = mContext.bindServiceAsUser(mIntent, this,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser);
+                        Context.BIND_AUTO_CREATE
+                                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+                                | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+                                | Context.BIND_WAIVE_PRIORITY,
+                        mUser);
             } catch (SecurityException e) {
                 Log.e(TAG, "Failed to bind to service", e);
                 mIsBound = false;
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 23f36e9..13cfa78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,7 +39,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -296,14 +296,16 @@
 
     @Override
     public boolean isLocked() {
-        KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
-        return keyguardMonitor.isShowing();
+        KeyguardStateController keyguardStateController =
+                Dependency.get(KeyguardStateController.class);
+        return keyguardStateController.isShowing();
     }
 
     @Override
     public boolean isSecure() {
-        KeyguardMonitor keyguardMonitor = Dependency.get(KeyguardMonitor.class);
-        return keyguardMonitor.isSecure() && keyguardMonitor.isShowing();
+        KeyguardStateController keyguardStateController =
+                Dependency.get(KeyguardStateController.class);
+        return keyguardStateController.isMethodSecure() && keyguardStateController.isShowing();
     }
 
     private CustomTile getTileForToken(IBinder token) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 31526bf..88c7964 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -14,10 +14,12 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
 import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -148,10 +150,15 @@
             iv.clearColorFilter();
         }
         if (state.state != mState) {
-            int color = getColor(state.state);
+            int color = getColor(state.state, state.colorActive);
             mState = state.state;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
-                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                if (colorIcon()) {
+                    animateColor(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                } else {
+                    animateGrayScale(mTint, color, iv,
+                            () -> updateIcon(iv, state, allowAnimations));
+                }
                 mTint = color;
             } else {
                 if (iv instanceof AlphaControlledSlashImageView) {
@@ -168,8 +175,12 @@
         }
     }
 
+    protected int getColor(int state, int colorActive) {
+        return getColorForState(getContext(), state, colorActive);
+    }
+
     protected int getColor(int state) {
-        return getColorForState(getContext(), state);
+        return getColor(state, -1);
     }
 
     private void animateGrayScale(int fromColor, int toColor, ImageView iv,
@@ -206,6 +217,37 @@
         }
     }
 
+    private void animateColor(int fromColor, int toColor, ImageView iv,
+            final Runnable endRunnable) {
+        if (iv instanceof AlphaControlledSlashImageView) {
+            ((AlphaControlledSlashImageView) iv)
+                    .setFinalImageTintList(ColorStateList.valueOf(toColor));
+        }
+        if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) {
+
+
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.setDuration(QS_ANIM_LENGTH);
+            anim.addUpdateListener(animation -> {
+                float fraction = animation.getAnimatedFraction();
+                int color = (int) ArgbEvaluator.getInstance().evaluate(fraction, fromColor,
+                        toColor);
+
+                setTint(iv, color);
+            });
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endRunnable.run();
+                }
+            });
+            anim.start();
+        } else {
+            setTint(iv, toColor);
+            endRunnable.run();
+        }
+    }
+
     public static void setTint(ImageView iv, int color) {
         iv.setImageTintList(ColorStateList.valueOf(color));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index c186e59..8b7f280 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -13,6 +13,8 @@
  */
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
+import static com.android.systemui.qs.QSColorControllerKt.overrideColor;
 import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
 
 import android.animation.ValueAnimator;
@@ -34,6 +36,7 @@
 import android.util.Log;
 import android.util.PathParser;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -41,6 +44,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Switch;
+import android.widget.TextView;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -65,12 +69,16 @@
     private boolean mShowRippleEffect = true;
 
     private final ImageView mBg;
+    private final TextView mDetailText;
     private final int mColorActive;
     private final int mColorInactive;
     private final int mColorDisabled;
     private int mCircleColor;
     private int mBgSize;
 
+    private final boolean mQsColors = overrideColor();
+    private final boolean mQSIcons = colorIcon();
+
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
     }
@@ -101,6 +109,12 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER);
         mIconFrame.addView(mIcon, params);
+
+        // "..." afforadance below icon
+        mDetailText = (TextView) LayoutInflater.from(context).inflate(R.layout.qs_tile_detail_text,
+                mIconFrame, false);
+        mIconFrame.addView(mDetailText);
+
         mIconFrame.setClipChildren(false);
         mIconFrame.setClipToPadding(false);
 
@@ -156,6 +170,10 @@
             tile.longClick();
             return true;
         });
+
+        if (tile.supportsDetailView()) {
+            mDetailText.setVisibility(View.VISIBLE);
+        }
     }
 
     public void init(OnClickListener click, OnClickListener secondaryClick,
@@ -194,7 +212,7 @@
     }
 
     protected void handleStateChanged(QSTile.State state) {
-        int circleColor = getCircleColor(state.state);
+        int circleColor = getCircleColor(state.state, mQsColors ? state.colorActive : -1);
         boolean allowAnimations = animationsEnabled();
         if (circleColor != mCircleColor) {
             if (allowAnimations) {
@@ -209,6 +227,8 @@
             mCircleColor = circleColor;
         }
 
+        mDetailText.setTextColor(QSTileImpl.getColorForState(getContext(), state.state));
+
         mShowRippleEffect = state.showRippleEffect;
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         setLongClickable(state.handlesLongClick);
@@ -239,10 +259,11 @@
         return mLocInScreen[1] >= -getHeight();
     }
 
-    private int getCircleColor(int state) {
+    private int getCircleColor(int state, int colorActive) {
         switch (state) {
             case Tile.STATE_ACTIVE:
-                return mColorActive;
+                int color = (colorActive == -1) ? mColorActive : colorActive;
+                return mQsColors && mQSIcons ? Utils.applyAlpha(0.5f, color) : color;
             case Tile.STATE_INACTIVE:
             case Tile.STATE_UNAVAILABLE:
                 return mColorDisabled;
@@ -252,6 +273,10 @@
         }
     }
 
+    private int getCircleColor(int state) {
+        return getCircleColor(state, -1);
+    }
+
     @Override
     public void setClickable(boolean clickable) {
         super.setClickable(clickable);
@@ -342,4 +367,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 9045539..681de37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -26,6 +26,7 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
 
 import android.app.ActivityManager;
 import android.content.Context;
@@ -54,6 +55,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
+import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
@@ -63,6 +65,8 @@
 import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.tiles.QSSettingsControllerKt;
+import com.android.systemui.qs.tiles.QSSettingsPanel;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -96,8 +100,8 @@
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mStaleListener = new Object();
-    protected TState mState = newTileState();
-    private TState mTmpState = newTileState();
+    protected TState mState;
+    private TState mTmpState;
     private boolean mAnnounceNextStateChange;
 
     private String mTileSpec;
@@ -121,9 +125,18 @@
      */
     abstract public int getMetricsCategory();
 
+    /**
+     * Experimental option on whether to use settings panels. Only loaded on creation, so the tile
+     * needs to be removed and added for this to take effect.
+     */
+    protected final QSSettingsPanel mQSSettingsPanelOption;
+
     protected QSTileImpl(QSHost host) {
         mHost = host;
         mContext = host.getContext();
+        mState = newTileState();
+        mTmpState = newTileState();
+        mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
     }
 
     @NonNull
@@ -288,6 +301,10 @@
     }
 
     protected void handleLongClick() {
+        if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
+            showDetail(true);
+            return;
+        }
         Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
                 getLongClickIntent(), 0);
     }
@@ -409,6 +426,10 @@
     public abstract CharSequence getTileLabel();
 
     public static int getColorForState(Context context, int state) {
+        return getColorForState(context, state, -1);
+    }
+
+    public static int getColorForState(Context context, int state, int colorActive) {
         switch (state) {
             case Tile.STATE_UNAVAILABLE:
                 return Utils.getDisabled(context,
@@ -416,13 +437,25 @@
             case Tile.STATE_INACTIVE:
                 return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
             case Tile.STATE_ACTIVE:
-                return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
+                return getActiveColor(context, colorActive);
             default:
                 Log.e("QSTile", "Invalid state " + state);
                 return 0;
         }
     }
 
+    private static int getActiveColor(Context context, int colorActive) {
+        if (colorIcon()) {
+            if (colorActive == -1) {
+                return Utils.getColorAccentDefaultColor(context);
+            } else {
+                return colorActive;
+            }
+        } else {
+            return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
+        }
+    }
+
     protected final class H extends Handler {
         private static final int ADD_CALLBACK = 1;
         private static final int CLICK = 2;
@@ -601,4 +634,27 @@
         pw.println(this.getClass().getSimpleName() + ":");
         pw.print("    "); pw.println(getState().toString());
     }
+
+    @Override
+    public void setColor(@ColorTile int color) {
+        int resId;
+        switch(color) {
+            case COLOR_TILE_RED:
+                resId = R.color.GM2_red_500;
+                break;
+            case COLOR_TILE_BLUE:
+                resId = R.color.GM2_blue_500;
+                break;
+            case COLOR_TILE_GREEN:
+                resId = R.color.GM2_green_500;
+                break;
+            case COLOR_TILE_YELLOW:
+                resId = R.color.GM2_yellow_500;
+                break;
+            default:
+                resId = -1;
+        }
+        mTmpState.colorActive = resId == -1 ? -1 : mContext.getColor(resId);
+        refreshState();
+    }
 }
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 b1dfbb5..0e813d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -45,7 +45,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 
 import java.util.ArrayList;
@@ -61,7 +61,7 @@
 
     private final CastController mController;
     private final CastDetailAdapter mDetailAdapter;
-    private final KeyguardMonitor mKeyguard;
+    private final KeyguardStateController mKeyguard;
     private final NetworkController mNetworkController;
     private final Callback mCallback = new Callback();
     private final ActivityStarter mActivityStarter;
@@ -69,12 +69,13 @@
     private boolean mWifiConnected;
 
     @Inject
-    public CastTile(QSHost host, CastController castController, KeyguardMonitor keyguardMonitor,
-            NetworkController networkController, ActivityStarter activityStarter) {
+    public CastTile(QSHost host, CastController castController,
+            KeyguardStateController keyguardStateController, NetworkController networkController,
+            ActivityStarter activityStarter) {
         super(host);
         mController = castController;
         mDetailAdapter = new CastDetailAdapter();
-        mKeyguard = keyguardMonitor;
+        mKeyguard = keyguardStateController;
         mNetworkController = networkController;
         mActivityStarter = activityStarter;
         mController.observe(this, mCallback);
@@ -257,7 +258,8 @@
                 }
             };
 
-    private final class Callback implements CastController.Callback, KeyguardMonitor.Callback {
+    private final class Callback implements CastController.Callback,
+            KeyguardStateController.Callback {
         @Override
         public void onCastDevicesChanged() {
             refreshState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 837ea9f..fbdca3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -28,7 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
 
@@ -40,16 +40,16 @@
     private final Icon mIcon = ResourceIcon.get(R.drawable.ic_location);
 
     private final LocationController mController;
-    private final KeyguardMonitor mKeyguard;
+    private final KeyguardStateController mKeyguard;
     private final ActivityStarter mActivityStarter;
     private final Callback mCallback = new Callback();
 
     @Inject
     public LocationTile(QSHost host, LocationController locationController,
-            KeyguardMonitor keyguardMonitor, ActivityStarter activityStarter) {
+            KeyguardStateController keyguardStateController, ActivityStarter activityStarter) {
         super(host);
         mController = locationController;
-        mKeyguard = keyguardMonitor;
+        mKeyguard = keyguardStateController;
         mActivityStarter = activityStarter;
         mController.observe(this, mCallback);
         mKeyguard.observe(this, mCallback);
@@ -71,7 +71,7 @@
 
     @Override
     protected void handleClick() {
-        if (mKeyguard.isSecure() && mKeyguard.isShowing()) {
+        if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 final boolean wasEnabled = mState.value;
                 mHost.openPanels();
@@ -97,7 +97,7 @@
 
         // Work around for bug 15916487: don't show location tile on top of lock screen. After the
         // bug is fixed, this should be reverted to only hiding it on secure lock screens:
-        // state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
+        // state.visible = !(mKeyguard.isMethodSecure() && mKeyguard.isShowing());
         state.value = locationEnabled;
         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_SHARE_LOCATION);
         if (state.disabledByPolicy == false) {
@@ -133,7 +133,7 @@
     }
 
     private final class Callback implements LocationChangeCallback,
-            KeyguardMonitor.Callback {
+            KeyguardStateController.Callback {
         @Override
         public void onLocationSettingsChanged(boolean enabled) {
             refreshState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
new file mode 100644
index 0000000..c7ef0be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.qs.tiles
+
+import android.provider.DeviceConfig
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+
+enum class QSSettingsPanel {
+    DEFAULT,
+    OPEN_LONG_PRESS,
+    OPEN_CLICK,
+    USE_DETAIL
+}
+
+fun getQSSettingsPanelOption(): QSSettingsPanel =
+        when (DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.QS_USE_SETTINGS_PANELS, 0)) {
+            1 -> QSSettingsPanel.OPEN_LONG_PRESS
+            2 -> QSSettingsPanel.OPEN_CLICK
+            3 -> QSSettingsPanel.USE_DETAIL
+            else -> QSSettingsPanel.DEFAULT
+        }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 91d38bd..187a3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -76,8 +76,11 @@
                 UserSwitcherController.UserRecord item) {
             UserDetailItemView v = UserDetailItemView.convertOrInflate(
                     mContext, convertView, parent);
-            if (v != convertView) {
+            if (!item.isCurrent || item.isGuest) {
                 v.setOnClickListener(this);
+            } else {
+                v.setOnClickListener(null);
+                v.setClickable(false);
             }
             String name = getName(mContext, item);
             if (item.picture == null) {
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 0e7362c3..b7ce101 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -56,6 +56,7 @@
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTileImpl<SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
+    private static final Intent WIFI_PANEL = new Intent(Settings.Panel.ACTION_WIFI);
 
     protected final NetworkController mController;
     private final AccessPointController mWifiController;
@@ -112,11 +113,21 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return WIFI_SETTINGS;
+        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_LONG_PRESS) return WIFI_PANEL;
+        else return WIFI_SETTINGS;
+    }
+
+    @Override
+    public boolean supportsDetailView() {
+        return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
     }
 
     @Override
     protected void handleClick() {
+        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK) {
+            mActivityStarter.postStartActivityDismissingKeyguard(WIFI_PANEL, 0);
+            return;
+        }
         // Secondary clicks are header clicks, just toggle.
         mState.copyTo(mStateBeforeClick);
         boolean wifiEnabled = mState.value;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9ac7ae9..681f3ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.admin.DevicePolicyManager;
@@ -59,8 +57,8 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
 import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -74,7 +72,7 @@
  * Controls the indications and error messages shown on the Keyguard
  */
 public class KeyguardIndicationController implements StateListener,
-        UnlockMethodCache.OnUnlockMethodChangedListener {
+        KeyguardStateController.Callback {
 
     private static final String TAG = "KeyguardIndication";
     private static final boolean DEBUG_CHARGING_SPEED = false;
@@ -88,12 +86,11 @@
     private final Context mContext;
     private final ShadeController mShadeController;
     private final AccessibilityController mAccessibilityController;
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTextView;
-    private KeyguardIndicationTextView mDisclosure;
     private final UserManager mUserManager;
     private final IBatteryStats mBatteryInfo;
     private final SettableWakeLock mWakeLock;
@@ -140,9 +137,9 @@
                 WakeLock.createPartial(context, "Doze:KeyguardIndication"),
                 Dependency.get(ShadeController.class),
                 Dependency.get(AccessibilityController.class),
-                UnlockMethodCache.getInstance(context),
+                Dependency.get(KeyguardStateController.class),
                 Dependency.get(StatusBarStateController.class),
-                KeyguardUpdateMonitor.getInstance(context));
+                Dependency.get(KeyguardUpdateMonitor.class));
     }
 
     /**
@@ -151,14 +148,15 @@
     @VisibleForTesting
     KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon,
             LockPatternUtils lockPatternUtils, WakeLock wakeLock, ShadeController shadeController,
-            AccessibilityController accessibilityController, UnlockMethodCache unlockMethodCache,
+            AccessibilityController accessibilityController,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mLockIcon = lockIcon;
         mShadeController = shadeController;
         mAccessibilityController = accessibilityController;
-        mUnlockMethodCache = unlockMethodCache;
+        mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         // lock icon is not used on all form factors.
@@ -180,12 +178,11 @@
         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
         setIndicationArea(indicationArea);
-        updateDisclosure();
 
         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
         mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
         mStatusBarStateController.addCallback(this);
-        mUnlockMethodCache.addListener(this);
+        mKeyguardStateController.addCallback(this);
     }
 
     public void setIndicationArea(ViewGroup indicationArea) {
@@ -193,7 +190,6 @@
         mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
         mInitialTextColorState = mTextView != null ?
                 mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
-        mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
         updateIndication(false /* animate */);
     }
 
@@ -231,27 +227,6 @@
         return mUpdateMonitorCallback;
     }
 
-    private void updateDisclosure() {
-        if (mDevicePolicyManager == null) {
-            return;
-        }
-
-        // TODO(b/140053632)
-        if (!mDozing && whitelistIpcs(mDevicePolicyManager::isDeviceManaged)) {
-            final CharSequence organizationName =
-                    mDevicePolicyManager.getDeviceOwnerOrganizationName();
-            if (organizationName != null) {
-                mDisclosure.switchIndication(mContext.getResources().getString(
-                        R.string.do_disclosure_with_name, organizationName));
-            } else {
-                mDisclosure.switchIndication(R.string.do_disclosure_generic);
-            }
-            mDisclosure.setVisibility(View.VISIBLE);
-        } else {
-            mDisclosure.setVisibility(View.GONE);
-        }
-    }
-
     public void setVisible(boolean visible) {
         mVisible = visible;
         mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE);
@@ -384,8 +359,7 @@
             int userId = KeyguardUpdateMonitor.getCurrentUser();
             String trustGrantedIndication = getTrustGrantedIndication();
             String trustManagedIndication = getTrustManagedIndication();
-            // TODO(b/140053632)
-            if (!whitelistIpcs(() -> mUserManager.isUserUnlocked(userId))) {
+            if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
                 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
                 mTextView.setTextColor(mInitialTextColorState);
             } else if (!TextUtils.isEmpty(mTransientIndication)) {
@@ -581,7 +555,6 @@
         }
         mDozing = dozing;
         updateIndication(false);
-        updateDisclosure();
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -611,7 +584,7 @@
     }
 
     @Override
-    public void onUnlockMethodStateChanged() {
+    public void onUnlockedChanged() {
         updateIndication(!mDozing);
     }
 
@@ -641,20 +614,12 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            if (showing) {
-                updateDisclosure();
-            }
-        }
-
-        @Override
         public void onBiometricHelp(int msgId, String helpString,
                 BiometricSourceType biometricSourceType) {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
             if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
                 return;
             }
-            animatePadlockError();
             boolean showSwipeToUnlock =
                     msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
@@ -675,7 +640,7 @@
         @Override
         public void onBiometricError(int msgId, String errString,
                 BiometricSourceType biometricSourceType) {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
             if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
                 return;
             }
@@ -757,6 +722,13 @@
         }
 
         @Override
+        public void onUserSwitchComplete(int userId) {
+            if (mVisible) {
+                updateIndication(false);
+            }
+        }
+
+        @Override
         public void onUserUnlocked() {
             if (mVisible) {
                 updateIndication(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
index 752b6a8..926ae71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
@@ -18,12 +18,19 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
 import android.service.notification.StatusBarNotification;
 import android.util.FeatureFlagUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
+import com.android.internal.R;
 import com.android.settingslib.media.MediaOutputSliceConstants;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.ActivityStarter;
@@ -51,7 +58,8 @@
             }
 
             ViewParent parent = view.getParent();
-            StatusBarNotification statusBarNotification = getNotificationForParent(parent);
+            StatusBarNotification statusBarNotification =
+                    getRowForParent(parent).getStatusBarNotification();
             final Intent intent = new Intent()
                     .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
                     .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
@@ -60,16 +68,6 @@
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
             return true;
         }
-
-        private StatusBarNotification getNotificationForParent(ViewParent parent) {
-            while (parent != null) {
-                if (parent instanceof ExpandableNotificationRow) {
-                    return ((ExpandableNotificationRow) parent).getStatusBarNotification();
-                }
-                parent = parent.getParent();
-            }
-            return null;
-        }
     };
 
     public MediaTransferManager(Context context) {
@@ -77,6 +75,16 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
     }
 
+    private ExpandableNotificationRow getRowForParent(ViewParent parent) {
+        while (parent != null) {
+            if (parent instanceof ExpandableNotificationRow) {
+                return ((ExpandableNotificationRow) parent);
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
     /**
      * apply the action button for MediaTransfer
      *
@@ -95,5 +103,23 @@
 
         view.setVisibility(View.VISIBLE);
         view.setOnClickListener(mOnClickHandler);
+
+        ExpandableNotificationRow enr = getRowForParent(view.getParent());
+        int color = enr.getNotificationHeader().getOriginalIconColor();
+        ColorStateList tintList = ColorStateList.valueOf(color);
+
+        // Update the outline color
+        LinearLayout viewLayout = (LinearLayout) view;
+        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
+        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+        rect.setStroke(2, color);
+
+        // Update the image color
+        ImageView image = view.findViewById(R.id.media_seamless_image);
+        image.setImageTintList(tintList);
+
+        // Update the text color
+        TextView text = view.findViewById(R.id.media_seamless_text);
+        text.setTextColor(tintList);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 7bcbd36..09f8045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -37,6 +37,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.systemui.Dependency;
+import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -233,4 +234,9 @@
     public NavigationBarFragment getDefaultNavigationBarFragment() {
         return mNavigationBars.get(DEFAULT_DISPLAY);
     }
+
+    /** @return {@link AssistHandleViewController} (only on the default display). */
+    public AssistHandleViewController getAssistHandlerViewController() {
+        return getDefaultNavigationBarFragment().getAssistHandlerViewController();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index 0f295ba..48e2923 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -27,6 +27,18 @@
     boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
 
     /**
+     * It's possible that a notification was canceled before it ever became visible. This callback
+     * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
+     * service is canceled too quickly but we still want to make sure a FGS notification shows.
+     * @param pendingEntry the canceled (but pending) entry
+     * @return true if the notification lifetime should be extended
+     */
+    default boolean shouldExtendLifetimeForPendingNotification(
+            @NonNull NotificationEntry pendingEntry) {
+        return false;
+    }
+
+    /**
      * Sets whether or not the lifetime should be managed by the extender.  In practice, if
      * shouldManage is true, this is where the extender starts managing the entry internally and is
      * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 107b24c..4ba1114 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -18,6 +18,7 @@
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -53,7 +54,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,7 +76,8 @@
 
     private final DeviceProvisionedController mDeviceProvisionedController =
             Dependency.get(DeviceProvisionedController.class);
-    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
 
     // Lazy
     private NotificationEntryManager mEntryManager;
@@ -281,7 +283,7 @@
         if (userId == UserHandle.USER_ALL) {
             userId = mCurrentUserId;
         }
-        return KeyguardUpdateMonitor.getInstance(mContext).isUserInLockdown(userId);
+        return Dependency.get(KeyguardUpdateMonitor.class).isUserInLockdown(userId);
     }
 
     /**
@@ -316,7 +318,7 @@
         boolean exceedsPriorityThreshold;
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            exceedsPriorityThreshold = entry.isTopBucket();
+            exceedsPriorityThreshold = entry.getBucket() != BUCKET_SILENT;
         } else {
             exceedsPriorityThreshold =
                     !getEntryManager().getNotificationData().isAmbient(entry.key);
@@ -506,8 +508,8 @@
         // asking if the keyguard is showing. We still need to check it though because showing the
         // camera on the keyguard has a state of SHADE but the keyguard is still showing.
         final boolean showingKeyguard = mState != StatusBarState.SHADE
-              || mKeyguardMonitor.isShowing();
-        final boolean devicePublic = showingKeyguard && isSecure(getCurrentUserId());
+                || mKeyguardStateController.isShowing();
+        final boolean devicePublic = showingKeyguard && mKeyguardStateController.isMethodSecure();
 
 
         // Look for public mode users. Users are considered public in either case of:
@@ -522,7 +524,7 @@
             boolean needsSeparateChallenge = whitelistIpcs(() ->
                     mLockPatternUtils.isSeparateProfileChallengeEnabled(userId));
             if (!devicePublic && userId != getCurrentUserId()
-                    && needsSeparateChallenge && isSecure(userId)) {
+                    && needsSeparateChallenge && mLockPatternUtils.isSecure(userId)) {
                 // Keyguard.isDeviceLocked is updated asynchronously, assume that all profiles
                 // with separate challenge are locked when keyguard is visible to avoid race.
                 isProfilePublic = showingKeyguard || mKeyguardManager.isDeviceLocked(userId);
@@ -544,7 +546,7 @@
 //        // asking if the keyguard is showing. We still need to check it though because showing the
 //        // camera on the keyguard has a state of SHADE but the keyguard is still showing.
 //        final boolean showingKeyguard = mState != StatusBarState.SHADE
-//              || mKeyguardMonitor.isShowing();
+//              || mKeyguardStateController.isShowing();
 //        final boolean devicePublic = showingKeyguard && isSecure(getCurrentUserId());
 //
 //
@@ -569,10 +571,6 @@
 //        }
 //    }
 
-    private boolean isSecure(int userId) {
-        return mKeyguardMonitor.isSecure() || mLockPatternUtils.isSecure(userId);
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 00a12a9..c50fb3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -17,10 +17,6 @@
 
 import static com.android.systemui.Dependency.MAIN_HANDLER;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_FADING;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
-import static com.android.systemui.statusbar.phone.BiometricUnlockController
-        .MODE_WAKE_AND_UNLOCK_PULSING;
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
 import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
 import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
@@ -67,7 +63,7 @@
 import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -94,7 +90,8 @@
     private final StatusBarStateController mStatusBarStateController
             = Dependency.get(StatusBarStateController.class);
     private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
-    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
     private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     static {
@@ -461,7 +458,7 @@
 
         boolean wakeAndUnlock = mBiometricUnlockController != null
             && mBiometricUnlockController.isWakeAndUnlock();
-        if (mKeyguardMonitor.isLaunchTransitionFadingAway() || wakeAndUnlock) {
+        if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
             mBackdrop.setVisibility(View.INVISIBLE);
             Trace.endSection();
             return;
@@ -599,7 +596,7 @@
                 boolean cannotAnimateDoze = shadeController != null
                         && shadeController.isDozing()
                         && !ScrimState.AOD.getAnimateChange();
-                boolean needsBypassFading = mKeyguardMonitor.isBypassFadingAnimation();
+                boolean needsBypassFading = mKeyguardStateController.isBypassFadingAnimation();
                 if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode()
                         == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                                 || cannotAnimateDoze) && !needsBypassFading)
@@ -626,10 +623,12 @@
                                 mBackdropBack.setImageDrawable(null);
                                 mHandler.post(mHideBackdropFront);
                             });
-                    if (mKeyguardMonitor.isKeyguardFadingAway()) {
+                    if (mKeyguardStateController.isKeyguardFadingAway()) {
                         mBackdrop.animate()
-                                .setDuration(mKeyguardMonitor.getShortenedFadingAwayDuration())
-                                .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
+                                .setDuration(
+                                        mKeyguardStateController.getShortenedFadingAwayDuration())
+                                .setStartDelay(
+                                        mKeyguardStateController.getKeyguardFadingAwayDelay())
                                 .setInterpolator(Interpolators.LINEAR)
                                 .start();
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index 3db02b9..266fe8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -26,8 +26,6 @@
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -39,22 +37,24 @@
 
     public final String key;
     public final List<Notification.Action> smartActions;
-    public final CharSequence[] smartReplies;
+    public final List<CharSequence> smartReplies;
 
     @VisibleForTesting
     NotificationUiAdjustment(
-            String key, List<Notification.Action> smartActions, CharSequence[] smartReplies) {
+            String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies) {
         this.key = key;
         this.smartActions = smartActions == null
                 ? Collections.emptyList()
-                : new ArrayList<>(smartActions);
-        this.smartReplies = smartReplies == null ? new CharSequence[0] : smartReplies.clone();
+                : smartActions;
+        this.smartReplies = smartReplies == null
+                ? Collections.emptyList()
+                : smartReplies;
     }
 
     public static NotificationUiAdjustment extractFromNotificationEntry(
             NotificationEntry entry) {
         return new NotificationUiAdjustment(
-                entry.key, entry.systemGeneratedSmartActions, entry.systemGeneratedSmartReplies);
+                entry.key, entry.getSmartActions(), entry.getSmartReplies());
     }
 
     public static boolean needReinflate(
@@ -66,7 +66,7 @@
         if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
             return true;
         }
-        if (!Arrays.equals(oldAdjustment.smartReplies, newAdjustment.smartReplies)) {
+        if (!newAdjustment.smartReplies.equals(oldAdjustment.smartReplies)) {
             return true;
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 6e75c03..50d9bae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -28,7 +28,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -85,7 +85,7 @@
      * possible.
      */
     private final boolean mAlwaysExpandNonGroupedNotification;
-    private final BubbleData mBubbleData;
+    private final BubbleController mBubbleController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
 
@@ -107,8 +107,8 @@
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
             Lazy<ShadeController> shadeController,
-            BubbleData bubbleData,
             KeyguardBypassController bypassController,
+            BubbleController bubbleController,
             DynamicPrivacyController privacyController) {
         mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,7 +121,7 @@
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mBubbleData = bubbleData;
+        mBubbleController = bubbleController;
         mDynamicPrivacyController = privacyController;
         privacyController.addListener(this);
     }
@@ -147,7 +147,7 @@
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
             if (ent.isRowDismissed() || ent.isRowRemoved()
-                    || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) {
+                    || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.key)) {
                 // we don't want to update removed notifications because they could
                 // temporarily become children if they were isolated before.
                 continue;
@@ -436,7 +436,7 @@
             }
 
             row.showAppOpsIcons(entry.mActiveAppOps);
-            row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs);
+            row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
         }
 
         Trace.beginSection("NotificationPresenter#onUpdateRowStates");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 2a5ccdb..843c37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -70,7 +70,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.registerCallback(mCallback);
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
         Dependency.get(NetworkController.class).addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 276afa7..a70dc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -56,7 +57,8 @@
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManagerPhone,
-    private val roundnessManager: NotificationRoundnessManager
+    private val roundnessManager: NotificationRoundnessManager,
+    private val statusBarStateController: StatusBarStateController
 ) : Gefingerpoken {
     companion object {
         private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -188,7 +190,8 @@
             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
             MotionEvent.ACTION_UP -> {
                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
-                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000
+                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
+                        statusBarStateController.state != StatusBarState.SHADE
                 if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 4422a81..8b9268e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -20,6 +20,7 @@
 import android.animation.ValueAnimator;
 import android.text.format.DateFormat;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -137,6 +138,11 @@
         // Record the to-be mState and mLastState
         recordHistoricalState(state, mState);
 
+        // b/139259891
+        if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
+            Log.e(TAG, "Invalid state transition: SHADE -> SHADE_LOCKED", new Throwable());
+        }
+
         synchronized (mListeners) {
             String tag = getClass().getSimpleName() + "#setState(" + state + ")";
             DejankUtils.startDetectingBlockingIpcs(tag);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 91d4707..13c6f27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
-import com.android.systemui.shared.system.SurfaceControlCompat;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -145,6 +144,7 @@
 
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+                RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
                 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
                     throws RemoteException {
             mSourceNotification.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index e4bd4fa..a0b144b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -16,16 +16,13 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.content.Context;
 import android.util.ArraySet;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -34,12 +31,11 @@
  * A controller which dynamically controls the visibility of Notification content
  */
 @Singleton
-public class DynamicPrivacyController implements UnlockMethodCache.OnUnlockMethodChangedListener {
+public class DynamicPrivacyController implements KeyguardStateController.Callback {
 
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final StatusBarStateController mStateController;
-    private final KeyguardMonitor mKeyguardMonitor;
     private ArraySet<Listener> mListeners = new ArraySet<>();
 
     private boolean mLastDynamicUnlocked;
@@ -47,30 +43,18 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Inject
-    DynamicPrivacyController(Context context,
-            KeyguardMonitor keyguardMonitor,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            StatusBarStateController stateController) {
-        this(notificationLockscreenUserManager, keyguardMonitor,
-                UnlockMethodCache.getInstance(context),
-                stateController);
-    }
-
-    @VisibleForTesting
     DynamicPrivacyController(NotificationLockscreenUserManager notificationLockscreenUserManager,
-            KeyguardMonitor keyguardMonitor,
-            UnlockMethodCache unlockMethodCache,
+            KeyguardStateController keyguardStateController,
             StatusBarStateController stateController) {
         mLockscreenUserManager = notificationLockscreenUserManager;
         mStateController = stateController;
-        mUnlockMethodCache = unlockMethodCache;
-        mKeyguardMonitor = keyguardMonitor;
-        mUnlockMethodCache.addListener(this);
+        mKeyguardStateController = keyguardStateController;
+        mKeyguardStateController.addCallback(this);
         mLastDynamicUnlocked = isDynamicallyUnlocked();
     }
 
     @Override
-    public void onUnlockMethodStateChanged() {
+    public void onUnlockedChanged() {
         if (isDynamicPrivacyEnabled()) {
             // We only want to notify our listeners if dynamic privacy is actually active
             boolean dynamicallyUnlocked = isDynamicallyUnlocked();
@@ -92,8 +76,9 @@
     }
 
     public boolean isDynamicallyUnlocked() {
-        return (mUnlockMethodCache.canSkipBouncer() || mKeyguardMonitor.isKeyguardGoingAway()
-                || mKeyguardMonitor.isKeyguardFadingAway())
+        return (mKeyguardStateController.canDismissLockScreen()
+                || mKeyguardStateController.isKeyguardGoingAway()
+                || mKeyguardStateController.isKeyguardFadingAway())
                 && isDynamicPrivacyEnabled();
     }
 
@@ -107,7 +92,7 @@
      */
     public boolean isInLockedDownShade() {
         if (!mStatusBarKeyguardViewManager.isShowing()
-                || !mUnlockMethodCache.isMethodSecure()) {
+                || !mKeyguardStateController.isMethodSecure()) {
             return false;
         }
         int state = mStateController.getState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index f3201ec..a5b7fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -55,7 +55,7 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
 
 import java.util.List;
@@ -64,7 +64,7 @@
  * splitted screen.
  */
 public class InstantAppNotifier extends SystemUI
-        implements CommandQueue.Callbacks, KeyguardMonitor.Callback {
+        implements CommandQueue.Callbacks, KeyguardStateController.Callback {
     private static final String TAG = "InstantAppNotifier";
     public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5;
 
@@ -72,13 +72,13 @@
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
     private boolean mDockedStackExists;
-    private KeyguardMonitor mKeyguardMonitor;
+    private KeyguardStateController mKeyguardStateController;
 
     public InstantAppNotifier() {}
 
     @Override
     public void start() {
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
 
         // listen for user / profile change.
         try {
@@ -88,7 +88,7 @@
         }
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
-        mKeyguardMonitor.addCallback(this);
+        mKeyguardStateController.addCallback(this);
 
         DockedStackExistsListener.register(
                 exists -> {
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 f8fef7d..b6b149d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -124,7 +124,7 @@
 
     @Inject
     public NotificationEntryManager(Context context) {
-        mNotificationData = new NotificationData();
+        mNotificationData = new NotificationData(context);
     }
 
     /** Adds a {@link NotificationEntryListener}. */
@@ -281,11 +281,25 @@
         }
 
         final NotificationEntry entry = mNotificationData.get(key);
-
-        abortExistingInflation(key);
-
         boolean lifetimeExtended = false;
 
+        // Notification was canceled before it got inflated
+        if (entry == null) {
+            NotificationEntry pendingEntry = mPendingNotifications.get(key);
+            if (pendingEntry != null) {
+                for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
+                    if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
+                        extendLifetime(pendingEntry, extender);
+                        lifetimeExtended = true;
+                    }
+                }
+            }
+        }
+
+        if (!lifetimeExtended) {
+            abortExistingInflation(key);
+        }
+
         if (entry != null) {
             // If a manager needs to keep the notification around for whatever reason, we
             // keep the notification
@@ -471,7 +485,7 @@
             NotificationUiAdjustment adjustment =
                     NotificationUiAdjustment.extractFromNotificationEntry(entry);
             oldAdjustments.put(entry.key, adjustment);
-            oldImportances.put(entry.key, entry.importance);
+            oldImportances.put(entry.key, entry.getImportance());
         }
 
         // Populate notification entries from the new rankings.
@@ -499,10 +513,10 @@
         if (rankingMap == null) {
             return;
         }
-        NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
+        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
         for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
-            rankingMap.getRanking(pendingNotification.key, tmpRanking);
-            pendingNotification.populateFromRanking(tmpRanking);
+            rankingMap.getRanking(pendingNotification.key, ranking);
+            pendingNotification.setRanking(ranking);
         }
     }
 
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 154d7b35..5a0b88c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -110,7 +110,7 @@
             return true;
         }
 
-        if (entry.suspended) {
+        if (entry.isSuspended()) {
             return true;
         }
 
@@ -133,11 +133,6 @@
                 }
             }
         }
-
-        if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
-            return true;
-        }
-
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 68d9546..9362d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
@@ -57,9 +56,8 @@
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    private final StatusBarStateController mStatusBarStateController =
-            Dependency.get(StatusBarStateController.class);
-    private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+    private final StatusBarStateController mStatusBarStateController;
+    private final NotificationFilter mNotificationFilter;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private final Context mContext;
@@ -67,7 +65,6 @@
     private final IDreamManager mDreamManager;
 
     private NotificationPresenter mPresenter;
-    private ShadeController mShadeController;
     private HeadsUpManager mHeadsUpManager;
     private HeadsUpSuppressor mHeadsUpSuppressor;
 
@@ -77,12 +74,15 @@
     private boolean mDisableNotificationAlerts;
 
     @Inject
-    public NotificationInterruptionStateProvider(Context context) {
+    public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+            StatusBarStateController stateController) {
         this(context,
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                 IDreamManager.Stub.asInterface(
                         ServiceManager.checkService(DreamService.DREAM_SERVICE)),
-                new AmbientDisplayConfiguration(context));
+                new AmbientDisplayConfiguration(context),
+                filter,
+                stateController);
     }
 
     @VisibleForTesting
@@ -90,11 +90,15 @@
             Context context,
             PowerManager powerManager,
             IDreamManager dreamManager,
-            AmbientDisplayConfiguration ambientDisplayConfiguration) {
+            AmbientDisplayConfiguration ambientDisplayConfiguration,
+            NotificationFilter notificationFilter,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+        mNotificationFilter = notificationFilter;
+        mStatusBarStateController = statusBarStateController;
     }
 
     /** Sets up late-binding dependencies for this component. */
@@ -102,29 +106,39 @@
             NotificationPresenter notificationPresenter,
             HeadsUpManager headsUpManager,
             HeadsUpSuppressor headsUpSuppressor) {
+        setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
+                new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        boolean wasUsing = mUseHeadsUp;
+                        mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                                && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                                Settings.Global.HEADS_UP_OFF);
+                        Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                        if (wasUsing != mUseHeadsUp) {
+                            if (!mUseHeadsUp) {
+                                Log.d(TAG,
+                                        "dismissing any existing heads up notification on disable"
+                                                + " event");
+                                mHeadsUpManager.releaseAllImmediately();
+                            }
+                        }
+                    }
+                });
+    }
+
+    /** Sets up late-binding dependencies for this component. */
+    public void setUpWithPresenter(
+            NotificationPresenter notificationPresenter,
+            HeadsUpManager headsUpManager,
+            HeadsUpSuppressor headsUpSuppressor,
+            ContentObserver observer) {
         mPresenter = notificationPresenter;
         mHeadsUpManager = headsUpManager;
         mHeadsUpSuppressor = headsUpSuppressor;
-
-        mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean wasUsing = mUseHeadsUp;
-                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                        Settings.Global.HEADS_UP_OFF);
-                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-                if (wasUsing != mUseHeadsUp) {
-                    if (!mUseHeadsUp) {
-                        Log.d(TAG,
-                                "dismissing any existing heads up notification on disable event");
-                        mHeadsUpManager.releaseAllImmediately();
-                    }
-                }
-            }
-        };
+        mHeadsUpObserver = observer;
 
         if (ENABLE_HEADS_UP) {
             mContext.getContentResolver().registerContentObserver(
@@ -138,13 +152,6 @@
         mHeadsUpObserver.onChange(true); // set up
     }
 
-    private ShadeController getShadeController() {
-        if (mShadeController == null) {
-            mShadeController = Dependency.get(ShadeController.class);
-        }
-        return mShadeController;
-    }
-
     /**
      * Whether the notification should appear as a bubble with a fly-out on top of the screen.
      *
@@ -153,7 +160,16 @@
      */
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.notification;
-        if (!entry.canBubble) {
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
+        if (!entry.canBubble()) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
             }
@@ -177,10 +193,6 @@
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
         return true;
     }
 
@@ -201,6 +213,21 @@
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
+        if (!mUseHeadsUp) {
+            if (DEBUG_HEADS_UP) {
+                Log.d(TAG, "No heads up: no huns");
+            }
+            return false;
+        }
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
         boolean inShade = mStatusBarStateController.getState() == SHADE;
         if (entry.isBubble() && inShade) {
             if (DEBUG_HEADS_UP) {
@@ -210,18 +237,14 @@
             return false;
         }
 
-        if (!canAlertCommon(entry)) {
+        if (entry.shouldSuppressPeek()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
             }
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
-        if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
+        if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
             if (DEBUG_HEADS_UP) {
                 Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
             }
@@ -284,7 +307,7 @@
             return false;
         }
 
-        if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) {
+        if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
             if (DEBUG_HEADS_UP) {
                 Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
             }
@@ -294,16 +317,13 @@
     }
 
     /**
-     * Common checks between regular heads up and when pulsing.  See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}.  Notifications that fail any of these
-     * checks
-     * should not alert at all.
+     * Common checks between regular & AOD heads up and bubbles.
      *
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    protected boolean canAlertCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
         if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -320,46 +340,36 @@
             }
             return false;
         }
-
         return true;
     }
 
     /**
-     * Common checks between heads up alerting and bubble fly out alerting. See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
-     * checks should not interrupt the user on screen.
+     * Common checks between alerts that occur while the device is awake (heads up & bubbles).
      *
      * @param entry the entry to check
-     * @return true if these checks pass, false if the notification should not interrupt on screen
+     * @return true if these checks pass, false if the notification should not alert
      */
-    public boolean canHeadsUpCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertAwakeCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
-        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+        if (mPresenter.isDeviceInVrMode()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: no huns or vr mode");
-            }
-            return false;
-        }
-
-        if (entry.shouldSuppressPeek()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+                Log.d(TAG, "No alerting: no huns or vr mode");
             }
             return false;
         }
 
         if (isSnoozedPackage(sbn)) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+                Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
             }
             return false;
         }
 
         if (entry.hasJustLaunchedFullScreenIntent()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+                Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
             }
             return false;
         }
@@ -377,6 +387,18 @@
         mHeadsUpObserver.onChange(true);
     }
 
+    /** Whether all alerts are disabled. */
+    @VisibleForTesting
+    public boolean areNotificationAlertsDisabled() {
+        return mDisableNotificationAlerts;
+    }
+
+    /** Whether HUNs should be used. */
+    @VisibleForTesting
+    public boolean getUseHeadsUp() {
+        return mUseHeadsUp;
+    }
+
     protected NotificationPresenter getPresenter() {
         return mPresenter;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 1af47dd..dfbbf98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -36,6 +36,7 @@
     private static final int[] sLocationOffset = new int[2];
 
     @Nullable private static Boolean sUseNewInterruptionModel = null;
+    @Nullable private static Boolean sUsePeopleFiltering = null;
 
     public static boolean isGrayscale(ImageView v, ContrastColorUtil colorUtil) {
         Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
@@ -87,4 +88,17 @@
         }
         return sUseNewInterruptionModel;
     }
+
+    /**
+     * Caches and returns the value of the people filtering setting. Cannot change except through
+     * process restarts.
+     */
+    public static boolean usePeopleFiltering(Context context) {
+        if (sUsePeopleFiltering == null) {
+            sUsePeopleFiltering = context.getResources().getBoolean(
+                    R.bool.config_usePeopleFiltering);
+        }
+
+        return sUsePeopleFiltering;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 4a6c7d2..6fe4abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -73,7 +73,7 @@
             @Override
             public void onPreEntryUpdated(NotificationEntry entry) {
                 final boolean mAmbientStateHasChanged =
-                        entry.ambient != entry.getRow().isLowPriority();
+                        entry.isAmbient() != entry.getRow().isLowPriority();
                 if (mAmbientStateHasChanged) {
                     mLowPriorityReorderingViews.add(entry);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 0009292..aacb2dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -16,10 +16,15 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.Person;
+import android.content.Context;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.SnoozeCriterion;
@@ -30,6 +35,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -44,6 +50,7 @@
  * The list of currently displaying notifications.
  */
 public class NotificationData {
+    private static final String TAG = "NotificationData";
 
     private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
 
@@ -64,6 +71,11 @@
 
     private RankingMap mRankingMap;
     private final Ranking mTmpRanking = new Ranking();
+    private final boolean mUsePeopleFiltering;
+
+    public NotificationData(Context context) {
+        mUsePeopleFiltering = NotificationUtils.usePeopleFiltering(context);
+    }
 
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
@@ -72,52 +84,25 @@
     @VisibleForTesting
     protected final Comparator<NotificationEntry> mRankingComparator =
             new Comparator<NotificationEntry>() {
-        private final Ranking mRankingA = new Ranking();
-        private final Ranking mRankingB = new Ranking();
-
         @Override
         public int compare(NotificationEntry a, NotificationEntry b) {
             final StatusBarNotification na = a.notification;
             final StatusBarNotification nb = b.notification;
-            int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
-            int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
-            int aRank = 0;
-            int bRank = 0;
+            int aRank = getRank(a.key);
+            int bRank = getRank(b.key);
 
-            if (mRankingMap != null) {
-                // RankingMap as received from NoMan
-                getRanking(a.key, mRankingA);
-                getRanking(b.key, mRankingB);
-                aImportance = mRankingA.getImportance();
-                bImportance = mRankingB.getImportance();
-                aRank = mRankingA.getRank();
-                bRank = mRankingB.getRank();
-            }
+            boolean aMedia = isImportantMedia(a);
+            boolean bMedia = isImportantMedia(b);
 
-            String mediaNotification = getMediaManager().getMediaNotificationKey();
+            boolean aSystemMax = isSystemMax(a);
+            boolean bSystemMax = isSystemMax(b);
 
-            // IMPORTANCE_MIN media streams are allowed to drift to the bottom
-            final boolean aMedia = a.key.equals(mediaNotification)
-                    && aImportance > NotificationManager.IMPORTANCE_MIN;
-            final boolean bMedia = b.key.equals(mediaNotification)
-                    && bImportance > NotificationManager.IMPORTANCE_MIN;
+            boolean aHeadsUp = a.isRowHeadsUp();
+            boolean bHeadsUp = b.isRowHeadsUp();
 
-            boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH
-                    && isSystemNotification(na);
-            boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH
-                    && isSystemNotification(nb);
-
-
-            boolean aHeadsUp = a.getRow().isHeadsUp();
-            boolean bHeadsUp = b.getRow().isHeadsUp();
-
-            // HACK: This should really go elsewhere, but it's currently not straightforward to
-            // extract the comparison code and we're guaranteed to touch every element, so this is
-            // the best place to set the buckets for the moment.
-            a.setIsTopBucket(aHeadsUp || aMedia || aSystemMax || a.isHighPriority());
-            b.setIsTopBucket(bHeadsUp || bMedia || bSystemMax || b.isHighPriority());
-
-            if (aHeadsUp != bHeadsUp) {
+            if (mUsePeopleFiltering && a.hasAssociatedPeople() != b.hasAssociatedPeople()) {
+                return a.hasAssociatedPeople() ? -1 : 1;
+            } else if (aHeadsUp != bHeadsUp) {
                 return aHeadsUp ? -1 : 1;
             } else if (aHeadsUp) {
                 // Provide consistent ranking with headsUpManager
@@ -213,7 +198,7 @@
             StatusBarNotification notification) {
         updateRanking(ranking);
         final StatusBarNotification oldNotification = entry.notification;
-        entry.notification = notification;
+        entry.setNotification(notification);
         mGroupManager.onEntryUpdated(entry, oldNotification);
     }
 
@@ -317,22 +302,6 @@
         return Ranking.VISIBILITY_NO_OVERRIDE;
     }
 
-    public int getImportance(String key) {
-        if (mRankingMap != null) {
-            getRanking(key, mTmpRanking);
-            return mTmpRanking.getImportance();
-        }
-        return NotificationManager.IMPORTANCE_UNSPECIFIED;
-    }
-
-    public String getOverrideGroupKey(String key) {
-        if (mRankingMap != null) {
-            getRanking(key, mTmpRanking);
-            return mTmpRanking.getOverrideGroupKey();
-        }
-        return null;
-    }
-
     public List<SnoozeCriterion> getSnoozeCriteria(String key) {
         if (mRankingMap != null) {
             getRanking(key, mTmpRanking);
@@ -357,6 +326,22 @@
         return 0;
     }
 
+    private boolean isImportantMedia(NotificationEntry e) {
+        int importance = e.ranking().getImportance();
+        boolean media = e.key.equals(getMediaManager().getMediaNotificationKey())
+                && importance > NotificationManager.IMPORTANCE_MIN;
+
+        return media;
+    }
+
+    private boolean isSystemMax(NotificationEntry e) {
+        int importance = e.ranking().getImportance();
+        boolean sys = importance  >= NotificationManager.IMPORTANCE_HIGH
+                && isSystemNotification(e.notification);
+
+        return sys;
+    }
+
     public boolean shouldHide(String key) {
         if (mRankingMap != null) {
             getRanking(key, mTmpRanking);
@@ -365,23 +350,25 @@
         return false;
     }
 
-    private void updateRankingAndSort(RankingMap ranking) {
-        if (ranking != null) {
-            mRankingMap = ranking;
+    private void updateRankingAndSort(RankingMap rankingMap) {
+        if (rankingMap != null) {
+            mRankingMap = rankingMap;
             synchronized (mEntries) {
                 final int len = mEntries.size();
                 for (int i = 0; i < len; i++) {
                     NotificationEntry entry = mEntries.valueAt(i);
-                    if (!getRanking(entry.key, mTmpRanking)) {
+                    Ranking newRanking = new Ranking();
+                    if (!getRanking(entry.key, newRanking)) {
                         continue;
                     }
+                    entry.setRanking(newRanking);
+
                     final StatusBarNotification oldSbn = entry.notification.cloneLight();
-                    final String overrideGroupKey = getOverrideGroupKey(entry.key);
+                    final String overrideGroupKey = newRanking.getOverrideGroupKey();
                     if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
                         entry.notification.setOverrideGroupKey(overrideGroupKey);
                         mGroupManager.onEntryUpdated(entry, oldSbn);
                     }
-                    entry.populateFromRanking(mTmpRanking);
                     entry.setIsHighPriority(isHighPriority(entry.notification));
                 }
             }
@@ -420,13 +407,37 @@
             }
         }
 
-        if (mSortedAndFiltered.size() == 1) {
-            // HACK: We need the comparator to run on all children in order to set the
-            // isHighPriority field. If there is only one child, then the comparison won't be run,
-            // so we have to trigger it manually. Get rid of this code as soon as possible.
-            mRankingComparator.compare(mSortedAndFiltered.get(0), mSortedAndFiltered.get(0));
+        Collections.sort(mSortedAndFiltered, mRankingComparator);
+
+        int bucket = BUCKET_PEOPLE;
+        for (NotificationEntry e : mSortedAndFiltered) {
+            assignBucketForEntry(e);
+            if (e.getBucket() < bucket) {
+                android.util.Log.wtf(TAG, "Detected non-contiguous bucket!");
+            }
+            bucket = e.getBucket();
+        }
+    }
+
+    private void assignBucketForEntry(NotificationEntry e) {
+        boolean isHeadsUp = e.isRowHeadsUp();
+        boolean isMedia = isImportantMedia(e);
+        boolean isSystemMax = isSystemMax(e);
+
+        setBucket(e, isHeadsUp, isMedia, isSystemMax);
+    }
+
+    private void setBucket(
+            NotificationEntry e,
+            boolean isHeadsUp,
+            boolean isMedia,
+            boolean isSystemMax) {
+        if (mUsePeopleFiltering && e.hasAssociatedPeople()) {
+            e.setBucket(BUCKET_PEOPLE);
+        } else if (isHeadsUp || isMedia || isSystemMax || e.isHighPriority()) {
+            e.setBucket(BUCKET_ALERTING);
         } else {
-            Collections.sort(mSortedAndFiltered, mRankingComparator);
+            e.setBucket(BUCKET_SILENT);
         }
     }
 
@@ -472,6 +483,19 @@
     }
 
     /**
+     * Get the current set of buckets for notification entries, as defined here
+     */
+    public static int[] getNotificationBuckets(Context context) {
+        if (NotificationUtils.usePeopleFiltering(context)) {
+            return new int[]{BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT};
+        } else if (NotificationUtils.useNewInterruptionModel(context)) {
+            return new int[]{BUCKET_ALERTING, BUCKET_SILENT};
+        } else {
+            return new int[]{BUCKET_ALERTING};
+        }
+    }
+
+    /**
      * Provides access to keyguard state and user settings dependent data.
      */
     public interface KeyguardEnvironment {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index fe88541..c3211e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -21,15 +21,20 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.Notification.EXTRA_MESSAGES;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+
 import android.annotation.NonNull;
 import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.app.Person;
@@ -38,10 +43,9 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 import android.widget.ImageView;
@@ -52,16 +56,15 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -86,13 +89,12 @@
     private static final long INITIALIZATION_DELAY = 400;
     private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
     private static final int COLOR_INVALID = 1;
+
     public final String key;
     public StatusBarNotification notification;
-    public NotificationChannel channel;
-    public long lastAudiblyAlertedMs;
+    private Ranking mRanking;
+
     public boolean noisy;
-    public boolean ambient;
-    public int importance;
     public StatusBarIconView icon;
     public StatusBarIconView expandedIcon;
     public StatusBarIconView centeredIcon;
@@ -102,14 +104,7 @@
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
-    public List<SnoozeCriterion> snoozeCriteria;
-    public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
-    /** Smart Actions provided by the NotificationAssistantService. */
-    @NonNull
-    public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
-    /** Smart replies provided by the NotificationAssistantService. */
-    @NonNull
-    public CharSequence[] systemGeneratedSmartReplies = new CharSequence[0];
+    private final List<Person> mAssociatedPeople = new ArrayList<>();
 
     /**
      * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
@@ -118,10 +113,6 @@
      */
     public EditedSuggestionInfo editedSuggestionInfo;
 
-    @VisibleForTesting
-    public int suppressedVisualEffects;
-    public boolean suspended;
-
     private NotificationEntry parent; // our parent (if we're in a group)
     private ExpandableNotificationRow row; // the outer expanded view
 
@@ -150,23 +141,10 @@
     private boolean hasSentReply;
 
     /**
-     * Whether this notification has been approved globally, at the app level, and at the channel
-     * level for bubbling.
+     * Whether this notification has changed in visual appearance since the previous post.
+     * New notifications are interruptive by default.
      */
-    public boolean canBubble;
-
-    /**
-     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
-     *
-     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
-     * expanded</p>
-     */
-    private boolean mShowInShadeWhenBubble;
-
-    /**
-     * Whether the user has dismissed this notification when it was in bubble form.
-     */
-    private boolean mUserDismissedBubble;
+    public boolean isVisuallyInterruptive;
 
     /**
      * Whether this notification is shown to the user as a high priority notification: visible on
@@ -174,44 +152,115 @@
      */
     private boolean mHighPriority;
 
-    private boolean mIsTopBucket;
-
     private boolean mSensitive = true;
     private Runnable mOnSensitiveChangedListener;
     private boolean mAutoHeadsUp;
     private boolean mPulseSupressed;
-
-    public NotificationEntry(StatusBarNotification n) {
-        this(n, null);
-    }
+    private int mBucket = BUCKET_ALERTING;
 
     public NotificationEntry(
-            StatusBarNotification n,
-            @Nullable NotificationListenerService.Ranking ranking) {
-        this.key = n.getKey();
-        this.notification = n;
-        if (ranking != null) {
-            populateFromRanking(ranking);
-        }
+            @NonNull StatusBarNotification sbn,
+            @NonNull Ranking ranking) {
+        this.key = sbn.getKey();
+        setNotification(sbn);
+        setRanking(ranking);
     }
 
-    public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
-        channel = ranking.getChannel();
-        lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
-        importance = ranking.getImportance();
-        ambient = ranking.isAmbient();
-        snoozeCriteria = ranking.getSnoozeCriteria();
-        userSentiment = ranking.getUserSentiment();
-        systemGeneratedSmartActions = ranking.getSmartActions() == null
-                ? Collections.emptyList() : ranking.getSmartActions();
-        systemGeneratedSmartReplies = ranking.getSmartReplies() == null
-                ? new CharSequence[0]
-                : ranking.getSmartReplies().toArray(new CharSequence[0]);
-        suppressedVisualEffects = ranking.getSuppressedVisualEffects();
-        suspended = ranking.isSuspended();
-        canBubble = ranking.canBubble();
+    /** The key for this notification. Guaranteed to be immutable and unique */
+    public String key() {
+        return key;
     }
 
+    /**
+     * The StatusBarNotification that represents one half of a NotificationEntry (the other half
+     * being the Ranking). This object is swapped out whenever a notification is updated.
+     */
+    public StatusBarNotification sbn() {
+        return notification;
+    }
+
+    /**
+     * Should only be called by NotificationEntryManager and friends.
+     * TODO: Make this package-private
+     */
+    public void setNotification(StatusBarNotification sbn) {
+        if (sbn.getKey() != null && key != null && !sbn.getKey().equals(key)) {
+            throw new IllegalArgumentException("New key " + sbn.getKey()
+                    + " doesn't match existing key " + key);
+        }
+        notification = sbn;
+        updatePeopleList();
+    }
+
+    /**
+     * The Ranking that represents one half of a NotificationEntry (the other half being the
+     * StatusBarNotification). This object is swapped out whenever a the ranking is updated (which
+     * generally occurs whenever anything changes in the notification list).
+     */
+    public Ranking ranking() {
+        return mRanking;
+    }
+
+    /**
+     * Should only be called by NotificationEntryManager and friends.
+     * TODO: Make this package-private
+     */
+    public void setRanking(@NonNull Ranking ranking) {
+        if (!ranking.getKey().equals(key)) {
+            throw new IllegalArgumentException("New key " + ranking.getKey()
+                    + " doesn't match existing key " + key);
+        }
+        mRanking = ranking;
+        isVisuallyInterruptive = ranking.visuallyInterruptive();
+    }
+
+    public NotificationChannel getChannel() {
+        return mRanking.getChannel();
+    }
+
+    public long getLastAudiblyAlertedMs() {
+        return mRanking.getLastAudiblyAlertedMillis();
+    }
+
+    public boolean isAmbient() {
+        return mRanking.isAmbient();
+    }
+
+    public int getImportance() {
+        return mRanking.getImportance();
+    }
+
+    public List<SnoozeCriterion> getSnoozeCriteria() {
+        return mRanking.getSnoozeCriteria();
+    }
+
+    public int getUserSentiment() {
+        return mRanking.getUserSentiment();
+    }
+
+    public int getSuppressedVisualEffects() {
+        return mRanking.getSuppressedVisualEffects();
+    }
+
+    public boolean isSuspended() {
+        return mRanking.isSuspended();
+    }
+
+    /** @see Ranking#canBubble() */
+    public boolean canBubble() {
+        return mRanking.canBubble();
+    }
+
+
+    public @NonNull List<Notification.Action> getSmartActions() {
+        return mRanking.getSmartActions();
+    }
+
+    public @NonNull List<CharSequence> getSmartReplies() {
+        return mRanking.getSmartReplies();
+    }
+
+
     public void setInterruption() {
         interruption = true;
     }
@@ -228,45 +277,39 @@
         this.mHighPriority = highPriority;
     }
 
-    /**
-     * @return True if the notif should appear in the "top" or "important" section of notifications
-     * (as opposed to the "bottom" or "silent" section). This is usually the same as
-     * {@link #isHighPriority()}, but there are certain exceptions, such as media notifs.
-     */
-    public boolean isTopBucket() {
-        return mIsTopBucket;
-    }
-    public void setIsTopBucket(boolean isTopBucket) {
-        mIsTopBucket = isTopBucket;
-    }
-
     public boolean isBubble() {
         return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
     }
 
-    public void setBubbleDismissed(boolean userDismissed) {
-        mUserDismissedBubble = userDismissed;
+    private void updatePeopleList() {
+        mAssociatedPeople.clear();
+
+        Bundle extras = notification.getNotification().extras;
+        if (extras == null) {
+            return;
+        }
+
+        List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
+
+        if (p != null) {
+            mAssociatedPeople.addAll(p);
+        }
+
+        if (Notification.MessagingStyle.class.equals(
+                notification.getNotification().getNotificationStyle())) {
+            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            if (!ArrayUtils.isEmpty(messages)) {
+                for (Notification.MessagingStyle.Message message :
+                        Notification.MessagingStyle.Message
+                                .getMessagesFromBundleArray(messages)) {
+                    mAssociatedPeople.add(message.getSenderPerson());
+                }
+            }
+        }
     }
 
-    public boolean isBubbleDismissed() {
-        return mUserDismissedBubble;
-    }
-
-    /**
-     * Sets whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public void setShowInShadeWhenBubble(boolean showInShade) {
-        mShowInShadeWhenBubble = showInShade;
-    }
-
-    /**
-     * Whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public boolean showInShadeWhenBubble() {
-        // We always show it in the shade if non-clearable
-        return !isRowDismissed() && (!isClearable() || mShowInShadeWhenBubble);
+    boolean hasAssociatedPeople() {
+        return mAssociatedPeople.size() > 0;
     }
 
     /**
@@ -285,6 +328,15 @@
         }
     }
 
+    @NotificationSectionsManager.PriorityBucket
+    public int getBucket() {
+        return mBucket;
+    }
+
+    public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+        mBucket = bucket;
+    }
+
     public ExpandableNotificationRow getRow() {
         return row;
     }
@@ -463,72 +515,6 @@
     }
 
     /**
-     * Returns our best guess for the most relevant text summary of the latest update to this
-     * notification, based on its type. Returns null if there should not be an update message.
-     */
-    public CharSequence getUpdateMessage(Context context) {
-        final Notification underlyingNotif = notification.getNotification();
-        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
-        try {
-            if (Notification.BigTextStyle.class.equals(style)) {
-                // Return the big text, it is big so probably important. If it's not there use the
-                // normal text.
-                CharSequence bigText =
-                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
-                return !TextUtils.isEmpty(bigText)
-                        ? bigText
-                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            } else if (Notification.MessagingStyle.class.equals(style)) {
-                final List<Notification.MessagingStyle.Message> messages =
-                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                                (Parcelable[]) underlyingNotif.extras.get(
-                                        Notification.EXTRA_MESSAGES));
-
-                final Notification.MessagingStyle.Message latestMessage =
-                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
-
-                if (latestMessage != null) {
-                    final CharSequence personName = latestMessage.getSenderPerson() != null
-                            ? latestMessage.getSenderPerson().getName()
-                            : null;
-
-                    // Prepend the sender name if available since group chats also use messaging
-                    // style.
-                    if (!TextUtils.isEmpty(personName)) {
-                        return context.getResources().getString(
-                                R.string.notification_summary_message_format,
-                                personName,
-                                latestMessage.getText());
-                    } else {
-                        return latestMessage.getText();
-                    }
-                }
-            } else if (Notification.InboxStyle.class.equals(style)) {
-                CharSequence[] lines =
-                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
-                // Return the last line since it should be the most recent.
-                if (lines != null && lines.length > 0) {
-                    return lines[lines.length - 1];
-                }
-            } else if (Notification.MediaStyle.class.equals(style)) {
-                // Return nothing, media updates aren't typically useful as a text update.
-                return null;
-            } else {
-                // Default to text extra.
-                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            }
-        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
-            // No use crashing, we'll just return null and the caller will assume there's no update
-            // message.
-            e.printStackTrace();
-        }
-
-        return null;
-    }
-
-    /**
      * Abort all existing inflation tasks
      */
     public void abortTask() {
@@ -589,21 +575,18 @@
         if (!ArrayUtils.isEmpty(replyTexts)) {
             return true;
         }
-        Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
-        if (messages != null && messages.length > 0) {
-            Parcelable message = messages[messages.length - 1];
-            if (message instanceof Bundle) {
-                Notification.MessagingStyle.Message lastMessage =
-                        Notification.MessagingStyle.Message.getMessageFromBundle(
-                                (Bundle) message);
-                if (lastMessage != null) {
-                    Person senderPerson = lastMessage.getSenderPerson();
-                    if (senderPerson == null) {
-                        return true;
-                    }
-                    Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
-                    return Objects.equals(user, senderPerson);
+        List<Message> messages = Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_MESSAGES));
+        if (messages != null && !messages.isEmpty()) {
+            Message lastMessage = messages.get(messages.size() -1);
+
+            if (lastMessage != null) {
+                Person senderPerson = lastMessage.getSenderPerson();
+                if (senderPerson == null) {
+                    return true;
                 }
+                Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+                return Objects.equals(user, senderPerson);
             }
         }
         return false;
@@ -832,7 +815,7 @@
         if (isExemptFromDndVisualSuppression()) {
             return false;
         }
-        return (suppressedVisualEffects & effect) != 0;
+        return (getSuppressedVisualEffects() & effect) != 0;
     }
 
     /**
@@ -875,6 +858,16 @@
         return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
     }
 
+
+    /**
+     * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
+     * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
+     * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
+     */
+    public boolean shouldSuppressNotificationDot() {
+        return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
+    }
+
     /**
      * Categories that are explicitly called out on DND settings screens are always blocked, if
      * DND has flagged them, even if they are foreground or system notifications that might
@@ -942,12 +935,4 @@
             this.index = index;
         }
     }
-
-    /**
-     * Returns whether the notification is a foreground service. It shows that this is an ongoing
-     * bubble.
-     */
-    public boolean isForegroundService() {
-        return (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 247c31f..60cf995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -214,7 +214,7 @@
                 // no-op here.
             }
         } else {
-            if (oldImportance != null && entry.importance != oldImportance) {
+            if (oldImportance != null && entry.getImportance() != oldImportance) {
                 if (entry.rowExists()) {
                     entry.getRow().onNotificationRankingUpdated();
                 }
@@ -228,7 +228,7 @@
             PackageManager pmUser,
             StatusBarNotification sbn,
             ExpandableNotificationRow row) {
-        row.setIsLowPriority(entry.ambient);
+        row.setIsLowPriority(entry.isAmbient());
 
         // Extract target SDK version.
         try {
@@ -248,7 +248,7 @@
         row.setOnActivatedListener(mPresenter);
 
         boolean useIncreasedCollapsedHeight =
-                mMessagingUtil.isImportantMessaging(sbn, entry.importance);
+                mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
         boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
                 && !mPresenter.isPresenterFullyCollapsed();
         row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
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 65e744b..0f6ce21 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
@@ -520,7 +520,7 @@
     public boolean getIsNonblockable() {
         boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
                 .isNonblockable(mStatusBarNotification.getPackageName(),
-                        mEntry.channel.getId());
+                        mEntry.getChannel().getId());
 
         // If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
         // again, but in-place on the main thread this time. This should rarely ever get called.
@@ -532,13 +532,13 @@
             mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification);
         }
 
-        isNonblockable |= mEntry.channel.isImportanceLockedByOEM();
-        isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction();
+        isNonblockable |= mEntry.getChannel().isImportanceLockedByOEM();
+        isNonblockable |= mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
 
         if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
             if (mEntry.mIsSystemNotification) {
-                if (mEntry.channel != null
-                        && !mEntry.channel.isBlockableSystem()) {
+                if (mEntry.getChannel() != null
+                        && !mEntry.getChannel().isBlockableSystem()) {
                     isNonblockable = true;
                 }
             }
@@ -2311,9 +2311,6 @@
 
     @Override
     public int getIntrinsicHeight() {
-        if (isShownAsBubble()) {
-            return getMaxExpandHeight();
-        }
         if (isUserLocked()) {
             return getActualHeight();
         }
@@ -2359,10 +2356,6 @@
         return mStatusbarStateController != null && mStatusbarStateController.isDozing();
     }
 
-    private boolean isShownAsBubble() {
-        return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
-    }
-
     @Override
     public boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -2396,7 +2389,7 @@
     public ArraySet<NotificationChannel> getUniqueChannels() {
         ArraySet<NotificationChannel> channels = new ArraySet<>();
 
-        channels.add(mEntry.channel);
+        channels.add(mEntry.getChannel());
 
         // If this is a summary, then add in the children notification channels for the
         // same user and pkg.
@@ -2405,7 +2398,7 @@
             final int numChildren = childrenRows.size();
             for (int i = 0; i < numChildren; i++) {
                 final ExpandableNotificationRow childRow = childrenRows.get(i);
-                final NotificationChannel childChannel = childRow.getEntry().channel;
+                final NotificationChannel childChannel = childRow.getEntry().getChannel();
                 final StatusBarNotification childSbn = childRow.getStatusBarNotification();
                 if (childSbn.getUser().equals(mStatusBarNotification.getUser()) &&
                         childSbn.getPackageName().equals(mStatusBarNotification.getPackageName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 2c15e87..73093c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -87,7 +87,7 @@
         // - The row is blockable (i.e. not non-blockable)
         // - The dismissed row is a valid group (>1 or 0 children from the same channel)
         // or the only child in the group
-        if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG)
+        if ((row.getEntry().getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG)
                 && mIsShadeExpanded
                 && !row.getIsNonblockable()
                 && ((!row.isChildInGroup() || row.isOnlyChildInGroup())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 48a8295..a612a17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -264,7 +264,7 @@
                 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
                 packageContext);
         result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
-                mRow.getContext(), mRow.getHeadsUpManager(),
+                mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                 mRow.getExistingSmartRepliesAndActions());
         apply(
                 inflateSynchronously,
@@ -311,20 +311,21 @@
 
     private static InflationProgress inflateSmartReplyViews(InflationProgress result,
             @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
-            HeadsUpManager headsUpManager, SmartRepliesAndActions previousSmartRepliesAndActions) {
+            Context packageContext, HeadsUpManager headsUpManager,
+            SmartRepliesAndActions previousSmartRepliesAndActions) {
         SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
         SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
         if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
             result.expandedInflatedSmartReplies =
                     InflatedSmartReplies.inflate(
-                            context, entry, smartReplyConstants, smartReplyController,
-                            headsUpManager, previousSmartRepliesAndActions);
+                            context, packageContext, entry, smartReplyConstants,
+                            smartReplyController, headsUpManager, previousSmartRepliesAndActions);
         }
         if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
             result.headsUpInflatedSmartReplies =
                     InflatedSmartReplies.inflate(
-                            context, entry, smartReplyConstants, smartReplyController,
-                            headsUpManager, previousSmartRepliesAndActions);
+                            context, packageContext, entry, smartReplyConstants,
+                            smartReplyController, headsUpManager, previousSmartRepliesAndActions);
         }
         return result;
     }
@@ -817,7 +818,7 @@
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
                 return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
-                        mRow.getContext(), mRow.getHeadsUpManager(),
+                        mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
                 mError = e;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 0c5b27b..f30a8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1139,19 +1139,21 @@
     }
 
     private void applyMediaTransfer(final NotificationEntry entry) {
+        if (!entry.isMediaNotification()) {
+            return;
+        }
+
         View bigContentView = mExpandedChild;
-        if (bigContentView == null || !entry.isMediaNotification()) {
-            return;
+        if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
+            mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView,
+                    entry);
         }
 
-        View mediaActionContainer = bigContentView.findViewById(
-                com.android.internal.R.id.media_actions);
-        if (!(mediaActionContainer instanceof LinearLayout)) {
-            return;
+        View smallContentView = mContractedChild;
+        if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
+            mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView,
+                    entry);
         }
-
-        mMediaTransferManager.applyMediaTransferView((ViewGroup) mediaActionContainer,
-                entry);
     }
 
     private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
@@ -1181,7 +1183,7 @@
                     mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
                             mCurrentSmartRepliesAndActions.smartActions.actions.size(),
                     mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
-                            mCurrentSmartRepliesAndActions.smartReplies.choices.length));
+                            mCurrentSmartRepliesAndActions.smartReplies.choices.size()));
         }
         applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
     }
@@ -1295,7 +1297,7 @@
                 if (smartRepliesAndActions.smartReplies != null
                         || smartRepliesAndActions.smartActions != null) {
                     int numSmartReplies = smartRepliesAndActions.smartReplies == null
-                            ? 0 : smartRepliesAndActions.smartReplies.choices.length;
+                            ? 0 : smartRepliesAndActions.smartReplies.choices.size();
                     int numSmartActions = smartRepliesAndActions.smartActions == null
                             ? 0 : smartRepliesAndActions.smartActions.actions.size();
                     boolean fromAssistant = smartRepliesAndActions.smartReplies == null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8f7671a..9d0dd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -241,7 +241,7 @@
 
         notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
         notificationSnoozeView.setStatusBarNotification(sbn);
-        notificationSnoozeView.setSnoozeOptions(row.getEntry().snoozeCriteria);
+        notificationSnoozeView.setSnoozeOptions(row.getEntry().getSnoozeCriteria());
         guts.setHeightChangedListener((NotificationGuts g) -> {
             mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
         });
@@ -316,7 +316,7 @@
                 iNotificationManager,
                 mVisualStabilityManager,
                 packageName,
-                row.getEntry().channel,
+                row.getEntry().getChannel(),
                 row.getUniqueChannels(),
                 sbn,
                 mCheckSaveListener,
@@ -325,7 +325,7 @@
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 isForBlockingHelper,
-                row.getEntry().importance,
+                row.getEntry().getImportance(),
                 row.getEntry().isHighPriority());
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4221846..ec0c634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -16,11 +16,10 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS;
-
-
+import android.content.Context;
 import android.util.MathUtils;
 
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,11 +50,14 @@
     private float mAppearFraction;
 
     @Inject
-    NotificationRoundnessManager(KeyguardBypassController keyguardBypassController) {
-        mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
+    NotificationRoundnessManager(
+            KeyguardBypassController keyguardBypassController,
+            Context context) {
+        int numberOfSections = NotificationData.getNotificationBuckets(context).length;
+        mFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mLastInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mTmpFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mTmpLastInSectionViews = new ActivatableNotificationView[numberOfSections];
         mBypassController = keyguardBypassController;
     }
 
@@ -157,7 +159,7 @@
 
     public void updateRoundedChildren(NotificationSection[] sections) {
         boolean anyChanged = false;
-        for (int i = 0; i < NUM_SECTIONS; i++) {
+        for (int i = 0; i < sections.length; i++) {
             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
             mTmpLastInSectionViews[i] = mLastInSectionViews[i];
             mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index f39ed2e..9d456ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -33,6 +33,7 @@
  * bounds change.
  */
 class NotificationSection {
+    private @NotificationSectionsManager.PriorityBucket int mBucket;
     private View mOwningView;
     private Rect mBounds = new Rect();
     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -43,8 +44,9 @@
     private ActivatableNotificationView mFirstVisibleChild;
     private ActivatableNotificationView mLastVisibleChild;
 
-    NotificationSection(View owningView) {
+    NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
         mOwningView = owningView;
+        mBucket = bucket;
     }
 
     public void cancelAnimators() {
@@ -72,6 +74,11 @@
         return mBottomAnimator != null || mTopAnimator != null;
     }
 
+    @NotificationSectionsManager.PriorityBucket
+    public int getBucket() {
+        return mBucket;
+    }
+
     public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) {
         // Left and right bounds are always applied immediately.
         mCurrentBounds.left = mBounds.left;
@@ -199,12 +206,16 @@
         return mLastVisibleChild;
     }
 
-    public void setFirstVisibleChild(ActivatableNotificationView child) {
+    public boolean setFirstVisibleChild(ActivatableNotificationView child) {
+        boolean changed = mFirstVisibleChild != child;
         mFirstVisibleChild = child;
+        return changed;
     }
 
-    public void setLastVisibleChild(ActivatableNotificationView child) {
+    public boolean setLastVisibleChild(ActivatableNotificationView child) {
+        boolean changed = mLastVisibleChild != child;
         mLastVisibleChild = child;
+        return changed;
     }
 
     public void resetCurrentBounds() {
@@ -291,5 +302,4 @@
         mBounds.bottom = bottom;
         return bottom;
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index d119fb79..d0444ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -18,6 +18,10 @@
 
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.provider.Settings;
@@ -34,23 +38,31 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Manages the boundaries of the two notification sections (high priority and low priority). Also
  * shows/hides the headers for those sections where appropriate.
  *
  * TODO: Move remaining sections logic from NSSL into this class.
  */
-class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+
+    private static final String TAG = "NotifSectionsManager";
+    private static final boolean DEBUG = false;
+
     private final NotificationStackScrollLayout mParent;
     private final ActivityStarter mActivityStarter;
     private final StatusBarStateController mStatusBarStateController;
     private final ConfigurationController mConfigurationController;
-    private final boolean mUseMultipleSections;
+    private final int mNumberOfSections;
 
     private boolean mInitialized = false;
     private SectionHeaderView mGentleHeader;
     private boolean mGentleHeaderVisible = false;
-    @Nullable private ExpandableNotificationRow mFirstGentleNotif;
+
     @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
 
     NotificationSectionsManager(
@@ -58,12 +70,21 @@
             ActivityStarter activityStarter,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
-            boolean useMultipleSections) {
+            int numberOfSections) {
         mParent = parent;
         mActivityStarter = activityStarter;
         mStatusBarStateController = statusBarStateController;
         mConfigurationController = configurationController;
-        mUseMultipleSections = useMultipleSections;
+        mNumberOfSections = numberOfSections;
+    }
+
+    NotificationSection[] createSectionsForBuckets(int[] buckets) {
+        NotificationSection[] sections = new NotificationSection[buckets.length];
+        for (int i = 0; i < buckets.length; i++) {
+            sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
+        }
+
+        return sections;
     }
 
     /** Must be called before use. */
@@ -111,8 +132,38 @@
     }
 
     @Override
-    public boolean beginsSection(View view) {
-        return view == getFirstLowPriorityChild();
+    public boolean beginsSection(@NonNull View view, @Nullable View previous) {
+        boolean begin = false;
+        if (view instanceof ExpandableNotificationRow) {
+            if (previous instanceof ExpandableNotificationRow) {
+                // If we're drawing the first non-person notification, break out a section
+                ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
+                ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+
+                begin =  curr.getEntry().getBucket() != prev.getEntry().getBucket();
+            }
+        }
+
+        if (!begin) {
+            begin = view == mGentleHeader;
+        }
+
+        return begin;
+    }
+
+    private boolean isUsingMultipleSections() {
+        return mNumberOfSections > 1;
+    }
+
+    private @PriorityBucket int getBucket(ActivatableNotificationView view)
+            throws IllegalArgumentException {
+        if (view instanceof ExpandableNotificationRow) {
+            return ((ExpandableNotificationRow) view).getEntry().getBucket();
+        } else if (view == mGentleHeader) {
+            return BUCKET_SILENT;
+        }
+
+        throw new IllegalArgumentException("I don't know how to find a bucket for this view :(");
     }
 
     /**
@@ -120,11 +171,10 @@
      * bookkeeping and adds/moves/removes section headers if appropriate.
      */
     void updateSectionBoundaries() {
-        if (!mUseMultipleSections) {
+        if (!isUsingMultipleSections()) {
             return;
         }
 
-        mFirstGentleNotif = null;
         int firstGentleNotifIndex = -1;
 
         final int n = mParent.getChildCount();
@@ -133,9 +183,8 @@
             if (child instanceof ExpandableNotificationRow
                     && child.getVisibility() != View.GONE) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!row.getEntry().isTopBucket()) {
+                if (row.getEntry().getBucket() == BUCKET_SILENT) {
                     firstGentleNotifIndex = i;
-                    mFirstGentleNotif = row;
                     break;
                 }
             }
@@ -184,80 +233,75 @@
     }
 
     /**
-     * Updates the boundaries (as tracked by their first and last views) of the high and low
-     * priority sections.
+     * Updates the boundaries (as tracked by their first and last views) of the priority sections.
      *
      * @return {@code true} If the last view in the top section changed (so we need to animate).
      */
-    boolean updateFirstAndLastViewsInSections(
-            final NotificationSection highPrioritySection,
-            final NotificationSection lowPrioritySection,
-            ActivatableNotificationView firstChild,
-            ActivatableNotificationView lastChild) {
-        if (mUseMultipleSections) {
-            ActivatableNotificationView previousLastHighPriorityChild =
-                    highPrioritySection.getLastVisibleChild();
-            ActivatableNotificationView previousFirstLowPriorityChild =
-                    lowPrioritySection.getFirstVisibleChild();
-            ActivatableNotificationView lastHighPriorityChild = getLastHighPriorityChild();
-            ActivatableNotificationView firstLowPriorityChild = getFirstLowPriorityChild();
-            if (lastHighPriorityChild != null && firstLowPriorityChild != null) {
-                highPrioritySection.setFirstVisibleChild(firstChild);
-                highPrioritySection.setLastVisibleChild(lastHighPriorityChild);
-                lowPrioritySection.setFirstVisibleChild(firstLowPriorityChild);
-                lowPrioritySection.setLastVisibleChild(lastChild);
-            } else if (lastHighPriorityChild != null) {
-                highPrioritySection.setFirstVisibleChild(firstChild);
-                highPrioritySection.setLastVisibleChild(lastChild);
-                lowPrioritySection.setFirstVisibleChild(null);
-                lowPrioritySection.setLastVisibleChild(null);
-            } else {
-                highPrioritySection.setFirstVisibleChild(null);
-                highPrioritySection.setLastVisibleChild(null);
-                lowPrioritySection.setFirstVisibleChild(firstChild);
-                lowPrioritySection.setLastVisibleChild(lastChild);
+    boolean updateFirstAndLastViewsForAllSections(
+            NotificationSection[] sections,
+            List<ActivatableNotificationView> children) {
+
+        if (sections.length <= 0 || children.size() <= 0) {
+            for (NotificationSection s : sections) {
+                s.setFirstVisibleChild(null);
+                s.setLastVisibleChild(null);
             }
-            return lastHighPriorityChild != previousLastHighPriorityChild
-                    || firstLowPriorityChild != previousFirstLowPriorityChild;
-        } else {
-            highPrioritySection.setFirstVisibleChild(firstChild);
-            highPrioritySection.setLastVisibleChild(lastChild);
             return false;
         }
+
+        boolean changed = false;
+        ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
+        for (NotificationSection s : sections) {
+            int filter = s.getBucket();
+            viewsInBucket.clear();
+
+            //TODO: do this in a single pass, and more better
+            for (ActivatableNotificationView v : children)  {
+                if (getBucket(v) == filter) {
+                    viewsInBucket.add(v);
+                }
+
+                if (viewsInBucket.size() >= 1) {
+                    changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
+                    changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
+                } else {
+                    changed |= s.setFirstVisibleChild(null);
+                    changed |= s.setLastVisibleChild(null);
+                }
+            }
+        }
+
+        if (DEBUG) {
+            logSections(sections);
+        }
+
+        return changed;
     }
 
+    private void logSections(NotificationSection[] sections) {
+        for (int i = 0; i < sections.length; i++) {
+            NotificationSection s = sections[i];
+            ActivatableNotificationView first = s.getFirstVisibleChild();
+            String fs = first == null ? "(null)"
+                    :  (first instanceof ExpandableNotificationRow)
+                            ? ((ExpandableNotificationRow) first).getEntry().key
+                            : Integer.toHexString(System.identityHashCode(first));
+            ActivatableNotificationView last = s.getLastVisibleChild();
+            String ls = last == null ? "(null)"
+                    :  (last instanceof ExpandableNotificationRow)
+                            ? ((ExpandableNotificationRow) last).getEntry().key
+                            : Integer.toHexString(System.identityHashCode(last));
+            android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
+            android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
+        }
+    }
+
+
     @VisibleForTesting
     SectionHeaderView getGentleHeaderView() {
         return mGentleHeader;
     }
 
-    @Nullable
-    private ActivatableNotificationView getFirstLowPriorityChild() {
-        if (mGentleHeaderVisible) {
-            return mGentleHeader;
-        } else {
-            return mFirstGentleNotif;
-        }
-    }
-
-    @Nullable
-    private ActivatableNotificationView getLastHighPriorityChild() {
-        ActivatableNotificationView lastChildBeforeGap = null;
-        int childCount = mParent.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mParent.getChildAt(i);
-            if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!row.getEntry().isTopBucket()) {
-                    break;
-                } else {
-                    lastChildBeforeGap = row;
-                }
-            }
-        }
-        return lastChildBeforeGap;
-    }
-
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onLocaleListChanged() {
@@ -279,4 +323,20 @@
             mOnClearGentleNotifsClickListener.onClick(v);
         }
     }
+
+    /**
+     * For now, declare the available notification buckets (sections) here so that other
+     * presentation code can decide what to do based on an entry's buckets
+     *
+     */
+    @Retention(SOURCE)
+    @IntDef(prefix = { "BUCKET_" }, value = {
+            BUCKET_PEOPLE,
+            BUCKET_ALERTING,
+            BUCKET_SILENT
+    })
+    public @interface PriorityBucket {}
+    public static final int BUCKET_PEOPLE = 0;
+    public static final int BUCKET_ALERTING = 1;
+    public static final int BUCKET_SILENT = 2;
 }
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 6a611a6..a67018e 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
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -112,6 +113,7 @@
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -172,7 +174,6 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
-    static final int NUM_SECTIONS = 2;
     /**
      * The distance in pixels between sections when the sections are directly adjacent (no visible
      * gap is drawn between them). In this case we don't want to round their corners.
@@ -351,7 +352,7 @@
             return true;
         }
     };
-    private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
+    private NotificationSection[] mSections;
     private boolean mAnimateNextBackgroundTop;
     private boolean mAnimateNextBackgroundBottom;
     private boolean mAnimateNextSectionBoundsChange;
@@ -522,9 +523,6 @@
 
         mAllowLongPress = allowLongPress;
 
-        for (int i = 0; i < NUM_SECTIONS; i++) {
-            mSections[i] = new NotificationSection(this);
-        }
         mRoundnessManager = notificationRoundnessManager;
 
         mHeadsUpManager = headsUpManager;
@@ -533,19 +531,21 @@
         mKeyguardBypassController = keyguardBypassController;
         mFalsingManager = falsingManager;
 
+        int[] buckets = NotificationData.getNotificationBuckets(context);
         mSectionsManager =
                 new NotificationSectionsManager(
                         this,
                         activityStarter,
                         statusBarStateController,
                         configurationController,
-                        NotificationUtils.useNewInterruptionModel(context));
+                        buckets.length);
         mSectionsManager.initialize(LayoutInflater.from(context));
         mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
             // Leave the shade open if there will be other notifs left over to clear
             final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
             clearNotifications(ROWS_GENTLE, closeShade);
         });
+        mSections = mSectionsManager.createSectionsForBuckets(buckets);
 
         mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
@@ -773,7 +773,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground
                 && (mSections[0].getCurrentBounds().top
-                < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
+                < mSections[mSections.length - 1].getCurrentBounds().bottom
                 || mAmbientState.isDozing())) {
             drawBackground(canvas);
         } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
@@ -819,7 +819,7 @@
         int lockScreenLeft = mSidePaddings;
         int lockScreenRight = getWidth() - mSidePaddings;
         int lockScreenTop = mSections[0].getCurrentBounds().top;
-        int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
+        int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
         int hiddenLeft = getWidth() / 2;
         int hiddenTop = mTopPadding;
 
@@ -2636,6 +2636,21 @@
         return null;
     }
 
+    //TODO: We shouldn't have to generate this list every time
+    private List<ActivatableNotificationView> getChildrenWithBackground() {
+        ArrayList<ActivatableNotificationView> children = new ArrayList<>();
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
+                children.add((ActivatableNotificationView) child);
+            }
+        }
+
+        return children;
+    }
+
     /**
      * Fling the scroll view
      *
@@ -3198,8 +3213,8 @@
 
         ActivatableNotificationView firstChild = getFirstChildWithBackground();
         ActivatableNotificationView lastChild = getLastChildWithBackground();
-        boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
-                mSections[0], mSections[1], firstChild, lastChild);
+        boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
+                mSections, getChildrenWithBackground());
 
         if (mAnimationsEnabled && mIsExpanded) {
             mAnimateNextBackgroundTop = firstChild != previousFirstChild;
@@ -5780,9 +5795,9 @@
             currentIndex++;
             boolean beforeSpeedBump;
             if (mHighPriorityBeforeSpeedBump) {
-                beforeSpeedBump = row.getEntry().isTopBucket();
+                beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT;
             } else {
-                beforeSpeedBump = !row.getEntry().ambient;
+                beforeSpeedBump = !row.getEntry().isAmbient();
             }
             if (beforeSpeedBump) {
                 speedBumpIndex = currentIndex;
@@ -5838,9 +5853,9 @@
             case ROWS_ALL:
                 return true;
             case ROWS_HIGH_PRIORITY:
-                return row.getEntry().isTopBucket();
+                return row.getEntry().getBucket() < BUCKET_SILENT;
             case ROWS_GENTLE:
-                return !row.getEntry().isTopBucket();
+                return row.getEntry().getBucket() == BUCKET_SILENT;
             default:
                 throw new IllegalArgumentException("Unknown selection: " + selection);
         }
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 ef80484..4b61064 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
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
@@ -411,8 +413,10 @@
             float currentYPosition,
             boolean reverse) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
+        ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
         final boolean applyGapHeight =
-                childNeedsGapHeight(ambientState.getSectionProvider(), algorithmState, i, child);
+                childNeedsGapHeight(
+                        ambientState.getSectionProvider(), algorithmState, i, child, previousChild);
         ExpandableViewState childViewState = child.getViewState();
         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
 
@@ -477,8 +481,11 @@
             SectionProvider sectionProvider,
             StackScrollAlgorithmState algorithmState,
             int visibleIndex,
-            View child) {
-        boolean needsGapHeight = sectionProvider.beginsSection(child) && visibleIndex > 0;
+            View child,
+            View previousChild) {
+
+        boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
+                && visibleIndex > 0;
         if (ANCHOR_SCROLLING) {
             needsGapHeight &= visibleIndex != algorithmState.anchorViewIndex;
         }
@@ -749,6 +756,6 @@
          * True if this view starts a new "section" of notifications, such as the gentle
          * notifications section. False if sections are not enabled.
          */
-        boolean beginsSection(View view);
+        boolean beginsSection(@NonNull View view, @Nullable View previous);
     }
 }
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 41c6a7b..7cbdfb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -127,7 +128,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private PowerManager.WakeLock mWakeLock;
     private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final Context mContext;
     private final int mWakeUpDelay;
@@ -150,11 +151,11 @@
             KeyguardViewMediator keyguardViewMediator,
             ScrimController scrimController,
             StatusBar statusBar,
-            UnlockMethodCache unlockMethodCache, Handler handler,
+            KeyguardStateController keyguardStateController, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController keyguardBypassController) {
         this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
-                unlockMethodCache, handler, keyguardUpdateMonitor,
+                keyguardStateController, handler, keyguardUpdateMonitor,
                 context.getResources()
                         .getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
                 keyguardBypassController);
@@ -162,14 +163,11 @@
 
     @VisibleForTesting
     protected BiometricUnlockController(Context context,
-                                     DozeScrimController dozeScrimController,
-                                     KeyguardViewMediator keyguardViewMediator,
-                                     ScrimController scrimController,
-                                     StatusBar statusBar,
-                                     UnlockMethodCache unlockMethodCache, Handler handler,
-                                     KeyguardUpdateMonitor keyguardUpdateMonitor,
-                                     int wakeUpDelay,
-                                     KeyguardBypassController keyguardBypassController) {
+            DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator,
+            ScrimController scrimController, StatusBar statusBar,
+            KeyguardStateController keyguardStateController, Handler handler,
+            KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
+            KeyguardBypassController keyguardBypassController) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -182,7 +180,7 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mScrimController = scrimController;
         mStatusBar = statusBar;
-        mUnlockMethodCache = unlockMethodCache;
+        mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mWakeUpDelay = wakeUpDelay;
         mKeyguardBypassController = keyguardBypassController;
@@ -416,7 +414,7 @@
                 return MODE_ONLY_WAKE;
             } else if (mDozeScrimController.isPulsing() && unlockingAllowed) {
                 return MODE_WAKE_AND_UNLOCK_PULSING;
-            } else if (unlockingAllowed || !mUnlockMethodCache.isMethodSecure()) {
+            } else if (unlockingAllowed || !mKeyguardStateController.isMethodSecure()) {
                 return MODE_WAKE_AND_UNLOCK;
             } else {
                 return MODE_SHOW_BOUNCER;
@@ -448,9 +446,7 @@
             } else if (!unlockingAllowed) {
                 return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
             } else if (mDozeScrimController.isPulsing()) {
-                // Let's not wake-up to lock screen when not bypassing, otherwise the notification
-                // would move as the user tried to tap it.
-                return bypass ? MODE_WAKE_AND_UNLOCK_PULSING : MODE_NONE;
+                return bypass ? MODE_WAKE_AND_UNLOCK_PULSING : MODE_ONLY_WAKE;
             } else {
                 if (bypass) {
                     // Wake-up fading out nicely
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index d655b2f..e78b85e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -38,7 +38,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
@@ -57,7 +57,7 @@
     public static final int FADE_IN_DELAY = 50;
     private PhoneStatusBarView mStatusBar;
     private StatusBarStateController mStatusBarStateController;
-    private KeyguardMonitor mKeyguardMonitor;
+    private KeyguardStateController mKeyguardStateController;
     private NetworkController mNetworkController;
     private LinearLayout mSystemIconArea;
     private View mClockView;
@@ -79,7 +79,7 @@
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mNetworkController = Dependency.get(NetworkController.class);
         mStatusBarStateController = Dependency.get(StatusBarStateController.class);
         mStatusBarComponent = SysUiServiceProvider.getComponent(getContext(), StatusBar.class);
@@ -207,8 +207,8 @@
             state |= DISABLE_CLOCK;
         }
 
-        if (!mKeyguardMonitor.isLaunchTransitionFadingAway()
-                && !mKeyguardMonitor.isKeyguardFadingAway()
+        if (!mKeyguardStateController.isLaunchTransitionFadingAway()
+                && !mKeyguardStateController.isKeyguardFadingAway()
                 && shouldHideNotificationIcons()
                 && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                         && headsUpVisible)) {
@@ -268,7 +268,7 @@
      * don't set the clock GONE otherwise it'll mess up the animation.
      */
     private int clockHiddenMode() {
-        if (!mStatusBar.isClosed() && !mKeyguardMonitor.isShowing()
+        if (!mStatusBar.isClosed() && !mKeyguardStateController.isShowing()
                 && !mStatusBarStateController.isDozing()) {
             return View.INVISIBLE;
         }
@@ -345,11 +345,11 @@
                 .withEndAction(null);
 
         // Synchronize the motion with the Keyguard fading if necessary.
-        if (mKeyguardMonitor.isKeyguardFadingAway()) {
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
             v.animate()
-                    .setDuration(mKeyguardMonitor.getKeyguardFadingAwayDuration())
+                    .setDuration(mKeyguardStateController.getKeyguardFadingAwayDuration())
                     .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
-                    .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
+                    .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
                     .start();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 575b559..4cd3ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -18,7 +18,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.content.Context;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -36,8 +35,6 @@
 import android.util.MathUtils;
 import android.util.StatsLog;
 import android.view.Gravity;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ISystemGestureExclusionListener;
 import android.view.InputChannel;
 import android.view.InputDevice;
@@ -57,6 +54,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -72,35 +70,13 @@
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
             "gestures.back_timeout", 250);
 
-    private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {
-        }
-
+    private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             // No need to thread jump, assignments are atomic
             mImeHeight = imeVisible ? imeHeight : 0;
             // TODO: Probably cancel any existing gesture
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-        }
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
-        }
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) {
-        }
     };
 
     private ISystemGestureExclusionListener mGestureExclusionListener =
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 f53c4e8..da62d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -39,7 +39,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import java.util.function.BiConsumer;
@@ -89,7 +89,7 @@
             };
     private boolean mAnimationsEnabled = true;
     Point mPoint;
-    private KeyguardMonitor mKeyguardMonitor;
+    private KeyguardStateController mKeyguardStateController;
 
 
     public HeadsUpAppearanceController(
@@ -160,7 +160,7 @@
         mWakeUpCoordinator = wakeUpCoordinator;
         wakeUpCoordinator.addListener(this);
         mCommandQueue = getComponent(headsUpStatusBarView.getContext(), CommandQueue.class);
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
     }
 
 
@@ -378,7 +378,7 @@
         boolean canShow = !mIsExpanded && notificationsShown;
         if (mBypassController.getBypassEnabled() &&
                 (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                        || mKeyguardMonitor.isKeyguardGoingAway())
+                        || mKeyguardStateController.isKeyguardGoingAway())
                 && notificationsShown) {
             canShow = true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 23b4814..68eca8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,7 +19,6 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -81,6 +80,7 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.PreviewInflater;
 import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
 import com.android.systemui.tuner.TunerService;
@@ -90,7 +90,7 @@
  * text.
  */
 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
-        UnlockMethodCache.OnUnlockMethodChangedListener,
+        KeyguardStateController.Callback,
         AccessibilityController.AccessibilityStateChangedCallback {
 
     final static String TAG = "StatusBar/KeyguardBottomAreaView";
@@ -120,7 +120,6 @@
     private KeyguardAffordanceView mRightAffordanceView;
     private KeyguardAffordanceView mLeftAffordanceView;
     private ViewGroup mIndicationArea;
-    private TextView mEnterpriseDisclosure;
     private TextView mIndicationText;
     private ViewGroup mPreviewContainer;
     private ViewGroup mOverlayContainer;
@@ -129,7 +128,7 @@
     private View mCameraPreview;
 
     private ActivityStarter mActivityStarter;
-    private UnlockMethodCache mUnlockMethodCache;
+    private KeyguardStateController mKeyguardStateController;
     private LockPatternUtils mLockPatternUtils;
     private FlashlightController mFlashlightController;
     private PreviewInflater mPreviewInflater;
@@ -234,16 +233,14 @@
         mRightAffordanceView = findViewById(R.id.camera_button);
         mLeftAffordanceView = findViewById(R.id.left_button);
         mIndicationArea = findViewById(R.id.keyguard_indication_area);
-        mEnterpriseDisclosure = findViewById(
-                R.id.keyguard_indication_enterprise_disclosure);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
         mIndicationBottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_margin_bottom);
         mBurnInYOffset = getResources().getDimensionPixelSize(
                 R.dimen.default_burn_in_prevention_offset);
         updateCameraVisibility();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
-        mUnlockMethodCache.addListener(this);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+        mKeyguardStateController.addCallback(this);
         setClipChildren(false);
         setClipToPadding(false);
         inflateCameraPreview();
@@ -280,17 +277,19 @@
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
                 UserHandle.ALL, filter, null, null);
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
+        mKeyguardStateController.addCallback(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
+        mKeyguardStateController.removeCallback(this);
         mAccessibilityController.removeStateChangedCallback(this);
         mRightExtension.destroy();
         mLeftExtension.destroy();
         getContext().unregisterReceiver(mDevicePolicyReceiver);
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     private void initAccessibility() {
@@ -312,9 +311,6 @@
         }
 
         // Respect font size setting.
-        mEnterpriseDisclosure.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.text_size_small_material));
         mIndicationText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                 getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.text_size_small_material));
@@ -365,11 +361,9 @@
      * Resolves the intent to launch the camera application.
      */
     public ResolveInfo resolveCameraIntent() {
-        // TODO(b/140057230)
-        return whitelistIpcs(() ->
-                mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
-                    PackageManager.MATCH_DEFAULT_ONLY,
-                    KeyguardUpdateMonitor.getCurrentUser()));
+        return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
+                PackageManager.MATCH_DEFAULT_ONLY,
+                KeyguardUpdateMonitor.getCurrentUser());
     }
 
     private void updateCameraVisibility() {
@@ -552,7 +546,7 @@
                 mAssistManager.launchVoiceAssistFromKeyguard();
             }
         };
-        if (mStatusBar.isKeyguardCurrentlySecure()) {
+        if (!mKeyguardStateController.canDismissLockScreen()) {
             AsyncTask.execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
@@ -617,7 +611,7 @@
     }
 
     @Override
-    public void onUnlockMethodStateChanged() {
+    public void onUnlockedChanged() {
         updateCameraVisibility();
     }
 
@@ -806,11 +800,11 @@
 
         @Override
         public IconState getIcon() {
-            ResolveInfo resolved = resolveCameraIntent();
             boolean isCameraDisabled = (mStatusBar != null) && !mStatusBar.isCameraAllowedByAdmin();
-            mIconState.isVisible = !isCameraDisabled && resolved != null
+            mIconState.isVisible = !isCameraDisabled
                     && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
-                    && mUserSetupComplete;
+                    && mUserSetupComplete
+                    && resolveCameraIntent() != null;
             mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
             mIconState.contentDescription =
                     mContext.getString(R.string.accessibility_camera_button);
@@ -819,13 +813,9 @@
 
         @Override
         public Intent getIntent() {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-            boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            // TODO(b/140057230)
-            boolean secure = whitelistIpcs(() ->
-                    mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()));
-            return (secure && !canSkipBouncer) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+            boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
+            boolean secure = mKeyguardStateController.isMethodSecure();
+            return (secure && !canDismissLs) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 2e7ba045c..c3de843 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,11 +41,12 @@
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.R;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DejankUtils;
+import com.android.systemui.R;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
 
@@ -69,7 +70,7 @@
     private final Handler mHandler;
     private final BouncerExpansionCallback mExpansionCallback;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
                 @Override
@@ -93,12 +94,12 @@
     private int mBouncerPromptReason;
     private boolean mIsAnimatingAway;
     private boolean mIsScrimmed;
-    private ViewGroup mLockIconContainer;
 
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils, ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
-            BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache,
+            BouncerExpansionCallback expansionCallback,
+            KeyguardStateController keyguardStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardBypassController keyguardBypassController, Handler handler) {
         mContext = context;
@@ -110,7 +111,7 @@
         mDismissCallbackRegistry = dismissCallbackRegistry;
         mExpansionCallback = expansionCallback;
         mHandler = handler;
-        mUnlockMethodCache = unlockMethodCache;
+        mKeyguardStateController = keyguardStateController;
         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
         mKeyguardBypassController = keyguardBypassController;
     }
@@ -174,7 +175,7 @@
 
         // Split up the work over multiple frames.
         DejankUtils.removeCallbacks(mResetRunnable);
-        if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer()
+        if (mKeyguardStateController.isFaceAuthEnabled() && !needsFullscreenBouncer()
                 && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
                 && !mKeyguardBypassController.getBypassEnabled()) {
             mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
@@ -190,10 +191,6 @@
         return mIsScrimmed;
     }
 
-    public ViewGroup getLockIconContainer() {
-        return mRoot == null || mRoot.getVisibility() != View.VISIBLE ? null : mLockIconContainer;
-    }
-
     /**
      * This method must be called at the end of the bouncer animation when
      * the translation is performed manually by the user, otherwise FalsingManager
@@ -374,11 +371,6 @@
 
     private void showPrimarySecurityScreen() {
         mKeyguardView.showPrimarySecurityScreen();
-        KeyguardSecurityView keyguardSecurityView = mKeyguardView.getCurrentSecurityView();
-        if (keyguardSecurityView != null) {
-            mLockIconContainer = ((ViewGroup) keyguardSecurityView)
-                    .findViewById(R.id.lock_icon_container);
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 832ea9e..aca7f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.tuner.TunerService
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -31,7 +32,7 @@
 @Singleton
 class KeyguardBypassController {
 
-    private val unlockMethodCache: UnlockMethodCache
+    private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
     private var hasFaceFeature: Boolean
 
@@ -47,7 +48,7 @@
      * If face unlock dismisses the lock screen or keeps user on keyguard for the current user.
      */
     var bypassEnabled: Boolean = false
-        get() = field && unlockMethodCache.isFaceAuthEnabled
+        get() = field && mKeyguardStateController.isFaceAuthEnabled
         private set
 
     var bouncerShowing: Boolean = false
@@ -66,9 +67,10 @@
         context: Context,
         tunerService: TunerService,
         statusBarStateController: StatusBarStateController,
-        lockscreenUserManager: NotificationLockscreenUserManager
+        lockscreenUserManager: NotificationLockscreenUserManager,
+        keyguardStateController: KeyguardStateController
     ) {
-        unlockMethodCache = UnlockMethodCache.getInstance(context)
+        this.mKeyguardStateController = keyguardStateController
         this.statusBarStateController = statusBarStateController
 
         hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index f4635d1..d9de59e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -16,23 +16,22 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.content.Context
 import android.hardware.Sensor
 import android.hardware.TriggerEvent
 import android.hardware.TriggerEventListener
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Dependency
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
-import com.android.systemui.util.AsyncSensorManager
+import com.android.systemui.util.sensors.AsyncSensorManager
 
 class KeyguardLiftController constructor(
-    context: Context,
     private val statusBarStateController: StatusBarStateController,
     private val asyncSensorManager: AsyncSensorManager
 ) : StatusBarStateController.StateListener, KeyguardUpdateMonitorCallback() {
 
-    private val keyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context)
+    private val keyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor::class.java)
     private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
     private var isListening = false
     private var bouncerVisible = false
@@ -67,6 +66,9 @@
     }
 
     private fun updateListeningState() {
+        if (pickupSensor == null) {
+            return
+        }
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index d709730..de660ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -31,7 +31,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -47,7 +47,7 @@
 
     private final Handler mHandler;
     private final DarkIntensityApplier mApplier;
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     private final StatusBarStateController mStatusBarStateController;
 
     private boolean mTransitionDeferring;
@@ -73,7 +73,7 @@
     public LightBarTransitionsController(Context context, DarkIntensityApplier applier) {
         mApplier = applier;
         mHandler = new Handler();
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mStatusBarStateController = Dependency.get(StatusBarStateController.class);
         SysUiServiceProvider.getComponent(context, CommandQueue.class)
                 .addCallback(this);
@@ -101,7 +101,7 @@
 
     @Override
     public void appTransitionPending(int displayId, boolean forced) {
-        if (mDisplayId != displayId || mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
+        if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
         mTransitionPending = true;
@@ -123,7 +123,7 @@
     @Override
     public void appTransitionStarting(int displayId, long startTime, long duration,
             boolean forced) {
-        if (mDisplayId != displayId || mKeyguardMonitor.isKeyguardGoingAway() && !forced) {
+        if (mDisplayId != displayId || mKeyguardStateController.isKeyguardGoingAway() && !forced) {
             return;
         }
         if (mTransitionPending && mTintChangePending) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 06a2225..4927ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
@@ -52,7 +53,7 @@
 import com.android.systemui.statusbar.phone.ScrimController.ScrimVisibility;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
@@ -67,9 +68,8 @@
  */
 public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener,
         StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
-        UnlockMethodCache.OnUnlockMethodChangedListener,
-        NotificationWakeUpCoordinator.WakeUpListener, ViewTreeObserver.OnPreDrawListener,
-        OnHeadsUpChangedListener {
+        KeyguardStateController.Callback, NotificationWakeUpCoordinator.WakeUpListener,
+        ViewTreeObserver.OnPreDrawListener, OnHeadsUpChangedListener {
 
     private static final int STATE_LOCKED = 0;
     private static final int STATE_LOCK_OPEN = 1;
@@ -77,11 +77,10 @@
     private static final int STATE_BIOMETRICS_ERROR = 3;
     private final ConfigurationController mConfigurationController;
     private final StatusBarStateController mStatusBarStateController;
-    private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final AccessibilityController mAccessibilityController;
     private final DockManager mDockManager;
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardBypassController mBypassController;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
     private final HeadsUpManagerPhone mHeadsUpManager;
@@ -104,14 +103,15 @@
     private boolean mShowingLaunchAffordance;
     private boolean mKeyguardJustShown;
     private boolean mUpdatePending;
+    private boolean mBouncerPreHideAnimation;
 
-    private final KeyguardMonitor.Callback mKeyguardMonitorCallback =
-            new KeyguardMonitor.Callback() {
+    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
+            new KeyguardStateController.Callback() {
                 @Override
                 public void onKeyguardShowingChanged() {
                     boolean force = false;
                     boolean wasShowing = mKeyguardShowing;
-                    mKeyguardShowing = mKeyguardMonitor.isShowing();
+                    mKeyguardShowing = mKeyguardStateController.isShowing();
                     if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
                         mBlockUpdates = false;
                         force = true;
@@ -124,11 +124,19 @@
 
                 @Override
                 public void onKeyguardFadingAwayChanged() {
-                    if (!mKeyguardMonitor.isKeyguardFadingAway() && mBlockUpdates) {
-                        mBlockUpdates = false;
-                        update(true /* force */);
+                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
+                        mBouncerPreHideAnimation = false;
+                        if (mBlockUpdates) {
+                            mBlockUpdates = false;
+                            update(true /* force */);
+                        }
                     }
                 }
+
+                @Override
+                public void onUnlockedChanged() {
+                    update();
+                }
             };
     private final DockManager.DockEventListener mDockEventListener =
             new DockManager.DockEventListener() {
@@ -176,19 +184,18 @@
             AccessibilityController accessibilityController,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
-            KeyguardMonitor keyguardMonitor,
+            KeyguardStateController keyguardStateController,
             @Nullable DockManager dockManager,
             HeadsUpManagerPhone headsUpManager) {
         super(context, attrs);
         mContext = context;
-        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAccessibilityController = accessibilityController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
         mBypassController = bypassController;
         mWakeUpCoordinator = wakeUpCoordinator;
-        mKeyguardMonitor = keyguardMonitor;
+        mKeyguardStateController = keyguardStateController;
         mDockManager = dockManager;
         mHeadsUpManager = headsUpManager;
     }
@@ -198,9 +205,8 @@
         super.onAttachedToWindow();
         mStatusBarStateController.addCallback(this);
         mConfigurationController.addCallback(this);
-        mKeyguardMonitor.addCallback(mKeyguardMonitorCallback);
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
         mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
-        mUnlockMethodCache.addListener(this);
         mWakeUpCoordinator.addListener(this);
         mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
         if (mDockManager != null) {
@@ -216,9 +222,8 @@
         mStatusBarStateController.removeCallback(this);
         mConfigurationController.removeCallback(this);
         mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
-        mKeyguardMonitor.removeCallback(mKeyguardMonitorCallback);
+        mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
         mWakeUpCoordinator.removeListener(this);
-        mUnlockMethodCache.removeListener(this);
         if (mDockManager != null) {
             mDockManager.removeListener(mDockEventListener);
         }
@@ -365,15 +370,15 @@
     }
 
     private boolean canBlockUpdates() {
-        return mKeyguardShowing || mKeyguardMonitor.isKeyguardFadingAway();
+        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
     }
 
     private void updateClickability() {
         if (mAccessibilityController == null) {
             return;
         }
-        boolean canLock = mUnlockMethodCache.isMethodSecure()
-                && mUnlockMethodCache.canSkipBouncer();
+        boolean canLock = mKeyguardStateController.isMethodSecure()
+                && mKeyguardStateController.canDismissLockScreen();
         boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
         setClickable(clickToUnlock);
         setLongClickable(canLock && !clickToUnlock);
@@ -463,6 +468,14 @@
         }
     }
 
+    /**
+     * Animate padlock opening when bouncer challenge is solved.
+     */
+    public void onBouncerPreHideAnimation() {
+        mBouncerPreHideAnimation = true;
+        update();
+    }
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ERROR, UNLOCK, LOCK, SCANNING})
     @interface LockAnimIndex {}
@@ -509,9 +522,9 @@
     }
 
     private int getState() {
-        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        if ((mUnlockMethodCache.canSkipBouncer() || !mKeyguardShowing
-                || mKeyguardMonitor.isKeyguardGoingAway()) && !mSimLocked) {
+        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
+                || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
             return STATE_LOCK_OPEN;
         } else if (mTransientBiometricsError) {
             return STATE_BIOMETRICS_ERROR;
@@ -569,11 +582,6 @@
         update(true /* force */);
     }
 
-    @Override
-    public void onUnlockMethodStateChanged() {
-        update();
-    }
-
     /**
      * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
      * icon on top of the black front scrim.
@@ -604,7 +612,7 @@
      */
     public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
         if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.VISIBILITY_FULLY_TRANSPARENT) {
+                && scrimsVisible == ScrimController.TRANSPARENT) {
             mWakeAndUnlockRunning = false;
             update();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index dcb349b..e34c639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -75,7 +75,7 @@
         mH = h;
         mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
         mCurrentUserId = ActivityManager.getCurrentUser();
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         IWallpaperManager service = IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
new file mode 100644
index 0000000..7dcc2fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.phone
+
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import com.android.systemui.plugins.NPVPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.qs.TouchAnimator
+import com.android.systemui.shared.plugins.PluginManager
+
+/**
+ * Manages the NPVPlugin view and state
+ *
+ * Abstracts NPVPlugin from NPV and helps animate on expansion and respond to changes in Config.
+ */
+class NPVPluginManager(
+    var parent: FrameLayout,
+    val pluginManager: PluginManager
+) : PluginListener<NPVPlugin> {
+
+    private var plugin: NPVPlugin? = null
+    private var animator = createAnimator()
+
+    private fun createAnimator() = TouchAnimator.Builder()
+            .addFloat(parent, "alpha", 1f, 0f)
+            .addFloat(parent, "scaleY", 1f, 0f)
+            .build()
+
+    init {
+        pluginManager.addPluginListener(NPVPlugin.ACTION, this, NPVPlugin::class.java, false)
+        parent.pivotY = 0f
+    }
+
+    override fun onPluginConnected(plugin: NPVPlugin, pluginContext: Context) {
+        parent.removeAllViews()
+        plugin.attachToRoot(parent)
+        this.plugin = plugin
+        parent.visibility = View.VISIBLE
+    }
+
+    fun changeVisibility(visibility: Int) {
+        parent.visibility = if (plugin != null) visibility else View.GONE
+    }
+
+    fun destroy() {
+        plugin?.onDestroy()
+        pluginManager.removePluginListener(this)
+    }
+
+    override fun onPluginDisconnected(plugin: NPVPlugin) {
+        if (this.plugin == plugin) {
+            this.plugin = null
+            parent.removeAllViews()
+            parent.visibility = View.GONE
+        }
+    }
+
+    fun setListening(listening: Boolean) {
+        plugin?.setListening(listening)
+    }
+
+    fun setExpansion(expansion: Float, headerTranslation: Float, heightDiff: Float) {
+        parent.setTranslationY(expansion * heightDiff + headerTranslation)
+        if (!expansion.isNaN()) animator.setPosition(expansion)
+    }
+
+    fun replaceFrameLayout(newParent: FrameLayout) {
+        newParent.visibility = parent.visibility
+        parent.removeAllViews()
+        plugin?.attachToRoot(newParent)
+        parent = newParent
+        animator = createAnimator()
+    }
+
+    fun getHeight() = if (plugin != null) parent.height else 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 2b8c86b..b87140d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -86,8 +86,8 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.ScreenDecorations;
 import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.assist.AssistHandleViewController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
@@ -174,7 +174,10 @@
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
     public boolean mHomeBlockedThisTouch;
-    private ScreenDecorations mScreenDecorations;
+
+    /** Only for default display */
+    @Nullable
+    private AssistHandleViewController mAssistHandlerViewController;
 
     private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER);
 
@@ -357,17 +360,22 @@
             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
         }
         setDisabled2Flags(mDisabledFlags2);
-
-        mScreenDecorations = SysUiServiceProvider.getComponent(getContext(),
-                ScreenDecorations.class);
-        getBarTransitions().addDarkIntensityListener(mScreenDecorations);
+        if (mIsOnDefaultDisplay) {
+            mAssistHandlerViewController =
+                new AssistHandleViewController(mHandler, mNavigationBarView);
+            getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController);
+        }
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
         if (mNavigationBarView != null) {
-            mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations);
+            if (mIsOnDefaultDisplay) {
+                mNavigationBarView.getBarTransitions()
+                        .removeDarkIntensityListener(mAssistHandlerViewController);
+                mAssistHandlerViewController = null;
+            }
             mNavigationBarView.getBarTransitions().destroy();
             mNavigationBarView.getLightTransitionsController().destroy(getContext());
         }
@@ -1019,6 +1027,11 @@
                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
     }
 
+    @Nullable
+    public AssistHandleViewController getAssistHandlerViewController() {
+        return mAssistHandlerViewController;
+    }
+
     /**
      * Performs transitions on navigation bar.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 195870b..adaea93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -53,12 +54,20 @@
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
     private boolean mIsUpdatingUnchangedGroup;
+    @Nullable private BubbleController mBubbleController = null;
 
     @Inject
     public NotificationGroupManager(StatusBarStateController statusBarStateController) {
         statusBarStateController.addCallback(this);
     }
 
+    private BubbleController getBubbleController() {
+        if (mBubbleController == null) {
+            mBubbleController = Dependency.get(BubbleController.class);
+        }
+        return mBubbleController;
+    }
+
     /**
      * Add a listener for changes to groups.
      *
@@ -187,12 +196,22 @@
         if (group == null) {
             return;
         }
+        int childCount = 0;
+        boolean hasBubbles = false;
+        for (String key : group.children.keySet()) {
+            if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) {
+                childCount++;
+            } else {
+                hasBubbles = true;
+            }
+        }
+
         boolean prevSuppressed = group.suppressed;
         group.suppressed = group.summary != null && !group.expanded
-                && (group.children.size() == 1
-                || (group.children.size() == 0
+                && (childCount == 1
+                || (childCount == 0
                         && group.summary.notification.getNotification().isGroupSummary()
-                        && hasIsolatedChildren(group)));
+                        && (hasIsolatedChildren(group) || hasBubbles)));
         if (prevSuppressed != group.suppressed) {
             for (OnGroupChangeListener listener : mListeners) {
                 if (!mIsUpdatingUnchangedGroup) {
@@ -381,6 +400,17 @@
     }
 
     /**
+     * If there is a {@link NotificationGroup} associated with the provided entry, this method
+     * will update the suppression of that group.
+     */
+    public void updateSuppression(NotificationEntry entry) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.notification));
+        if (group != null) {
+            updateSuppression(group);
+        }
+    }
+
+    /**
      * Get the group key. May differ from the one in the notification due to the notification
      * being temporarily isolated.
      *
@@ -565,6 +595,7 @@
                         ? Log.getStackTraceString(child.getDebugThrowable())
                         : "");
             }
+            result += "\n    summary suppressed: " + suppressed;
             return result;
         }
     }
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 ba34069..1a3560e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -537,11 +537,7 @@
         if (dozeParameters.shouldControlScreenOff()) {
             mAodIcons.setTranslationY(-mAodIconAppearTranslation);
             mAodIcons.setAlpha(0);
-            mAodIcons.animate()
-                    .setInterpolator(Interpolators.DECELERATE_QUINT)
-                    .translationY(0)
-                    .setDuration(AOD_ICONS_APPEAR_DURATION)
-                    .start();
+            animateInAodIconTranslation();
             mAodIcons.animate()
                     .alpha(1)
                     .setInterpolator(Interpolators.LINEAR)
@@ -550,6 +546,14 @@
         }
     }
 
+    private void animateInAodIconTranslation() {
+        mAodIcons.animate()
+                .setInterpolator(Interpolators.DECELERATE_QUINT)
+                .translationY(0)
+                .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .start();
+    }
+
     private void reloadAodColor() {
         mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
@@ -606,14 +610,19 @@
                         mAodIcons.setAlpha(1.0f);
                         appearAodIcons();
                     } else {
+                        // Let's make sure the icon are translated to 0, since we cancelled it above
+                        animateInAodIconTranslation();
                         // We were fading out, let's fade in instead
                         CrossFadeHelper.fadeIn(mAodIcons);
                     }
                 } else {
+                    // Let's make sure the icon are translated to 0, since we cancelled it above
+                    animateInAodIconTranslation();
                     CrossFadeHelper.fadeOut(mAodIcons);
                 }
             } else {
                 mAodIcons.setAlpha(1.0f);
+                mAodIcons.setTranslationY(0);
                 mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 248bc75..86da10a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -39,6 +39,7 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
@@ -62,6 +63,7 @@
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
@@ -100,6 +102,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -172,6 +175,46 @@
             R.id.keyguard_hun_animator_start_tag);
     private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    @VisibleForTesting
+    final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+
+                @Override
+                public void onBiometricAuthenticated(int userId,
+                        BiometricSourceType biometricSourceType) {
+                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        mDelayShowingKeyguardStatusBar = true;
+                    }
+                }
+
+                @Override
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
+                    boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
+                            || mBarState == StatusBarState.SHADE_LOCKED;
+                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+                            && !mDelayShowingKeyguardStatusBar) {
+                        mFirstBypassAttempt = false;
+                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    }
+                }
+
+                @Override
+                public void onFinishedGoingToSleep(int why) {
+                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+                    mDelayShowingKeyguardStatusBar = false;
+                }
+            };
+    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardFadingAwayChanged() {
+                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
+                        mFirstBypassAttempt = false;
+                        mDelayShowingKeyguardStatusBar = false;
+                    }
+                }
+            };
 
     private final InjectionInflationController mInjectionInflationController;
     private final PowerManager mPowerManager;
@@ -393,6 +436,21 @@
     private boolean mAllowExpandForSmallExpansion;
     private Runnable mExpandAfterLayoutRunnable;
 
+    /**
+     * If face auth with bypass is running for the first time after you turn on the screen.
+     * (From aod or screen off)
+     */
+    private boolean mFirstBypassAttempt;
+    /**
+     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+     * the keyguard is dismissed to show the status bar.
+     */
+    private boolean mDelayShowingKeyguardStatusBar;
+
+    private PluginManager mPluginManager;
+    private FrameLayout mPluginFrame;
+    private NPVPluginManager mNPVPluginManager;
+
     @Inject
     public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             InjectionInflationController injectionInflationController,
@@ -400,7 +458,8 @@
             PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardBypassController bypassController,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            PluginManager pluginManager) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
@@ -421,7 +480,9 @@
         });
         mThemeResId = context.getThemeResId();
         mKeyguardBypassController = bypassController;
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
         dynamicPrivacyController.addListener(this);
 
         mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -431,6 +492,7 @@
         });
         mBottomAreaShadeAlphaAnimator.setDuration(160);
         mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+        mPluginManager = pluginManager;
     }
 
     /**
@@ -465,6 +527,9 @@
         mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
         mLastOrientation = getResources().getConfiguration().orientation;
+        mPluginFrame = findViewById(R.id.plugin_frame);
+        mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+
 
         initBottomArea();
 
@@ -510,6 +575,7 @@
         Dependency.get(StatusBarStateController.class).addCallback(this);
         Dependency.get(ZenModeController.class).addCallback(this);
         Dependency.get(ConfigurationController.class).addCallback(this);
+        mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
         // Theme might have changed between inflating this view and attaching it to the window, so
         // force a call to onThemeChanged
         onThemeChanged();
@@ -522,6 +588,7 @@
         Dependency.get(StatusBarStateController.class).removeCallback(this);
         Dependency.get(ZenModeController.class).removeCallback(this);
         Dependency.get(ConfigurationController.class).removeCallback(this);
+        mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
     }
 
     @Override
@@ -584,6 +651,19 @@
             lp.gravity = panelGravity;
             mNotificationStackScroller.setLayoutParams(lp);
         }
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin =
+                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
     }
 
     @Override
@@ -650,6 +730,43 @@
         if (mOnReinflationListener != null) {
             mOnReinflationListener.run();
         }
+        reinflatePluginContainer();
+    }
+
+    @Override
+    public void onUiModeChanged() {
+        reinflatePluginContainer();
+    }
+
+    private void reinflatePluginContainer() {
+        int index = indexOfChild(mPluginFrame);
+        removeView(mPluginFrame);
+        mPluginFrame = (FrameLayout) mInjectionInflationController
+                .injectable(LayoutInflater.from(mContext)).inflate(
+                        R.layout.status_bar_expanded_plugin_frame,
+                        this,
+                        false);
+        addView(mPluginFrame, index);
+
+        Resources res = getResources();
+        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp;
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin =
+                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+
+        mNPVPluginManager.replaceFrameLayout(mPluginFrame);
     }
 
     private void initBottomArea() {
@@ -679,6 +796,7 @@
         int oldMaxHeight = mQsMaxExpansionHeight;
         if (mQs != null) {
             mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+            mQsMinExpansionHeight += mNPVPluginManager.getHeight();
             mQsMaxExpansionHeight = mQs.getDesiredHeight();
             mNotificationStackScroller.setMaxTopPadding(
                     mQsMaxExpansionHeight + mQsNotificationTopPadding);
@@ -1565,7 +1683,7 @@
     @Override
     public void onStateChanged(int statusBarState) {
         boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
-        boolean keyguardFadingAway = mKeyguardMonitor.isKeyguardFadingAway();
+        boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
         int oldState = mBarState;
         boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
         setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
@@ -1583,7 +1701,7 @@
                 && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
             animateKeyguardStatusBarOut();
             long delay = mBarState == StatusBarState.SHADE_LOCKED
-                    ? 0 : mKeyguardMonitor.calculateGoingToFullShadeDelay();
+                    ? 0 : mKeyguardStateController.calculateGoingToFullShadeDelay();
             mQs.animateHeaderSlidingIn(delay);
         } else if (oldState == StatusBarState.SHADE_LOCKED
                 && statusBarState == StatusBarState.KEYGUARD) {
@@ -1660,13 +1778,13 @@
     private void animateKeyguardStatusBarOut() {
         ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
         anim.addUpdateListener(mStatusBarAnimateAlphaListener);
-        anim.setStartDelay(mKeyguardMonitor.isKeyguardFadingAway()
-                ? mKeyguardMonitor.getKeyguardFadingAwayDelay()
+        anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
+                ? mKeyguardStateController.getKeyguardFadingAwayDelay()
                 : 0);
 
         long duration;
-        if (mKeyguardMonitor.isKeyguardFadingAway()) {
-            duration = mKeyguardMonitor.getShortenedFadingAwayDuration();
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            duration = mKeyguardStateController.getShortenedFadingAwayDuration();
         } else {
             duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
         }
@@ -1713,8 +1831,8 @@
         if (goingToFullShade) {
             mKeyguardBottomArea.animate()
                     .alpha(0f)
-                    .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
-                    .setDuration(mKeyguardMonitor.getShortenedFadingAwayDuration())
+                    .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                    .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
                     .setInterpolator(Interpolators.ALPHA_OUT)
                     .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
                     .start();
@@ -1742,8 +1860,8 @@
                     .withEndAction(mAnimateKeyguardStatusViewGoneEndRunnable);
             if (keyguardFadingAway) {
                 mKeyguardStatusView.animate()
-                        .setStartDelay(mKeyguardMonitor.getKeyguardFadingAwayDelay())
-                        .setDuration(mKeyguardMonitor.getShortenedFadingAwayDuration())
+                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
                         .start();
             }
         } else if (mBarState == StatusBarState.SHADE_LOCKED
@@ -1784,6 +1902,9 @@
                 mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
         updateEmptyShadeView();
+        mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+                ? View.VISIBLE
+                : View.INVISIBLE);
         mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
                 && !mStackScrollerOverscrolling && mQsScrimEnabled
                 ? View.VISIBLE
@@ -1798,7 +1919,8 @@
     private void setQsExpansion(float height) {
         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
         mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
-        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
+        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
+                && !mDozing) {
             setQsExpanded(true);
         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
             setQsExpanded(false);
@@ -1839,6 +1961,8 @@
         if (mQs == null) return;
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+        mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
         mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
     }
 
@@ -2259,6 +2383,7 @@
                 appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
             }
             startHeight = -mQs.getQsMinExpansionHeight();
+            startHeight -= mNPVPluginManager.getHeight();
         }
         float translation = MathUtils.lerp(startHeight, 0,
                 Math.min(1.0f, appearAmount))
@@ -2299,7 +2424,10 @@
                 * mKeyguardStatusBarAnimateAlpha;
         newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
         mKeyguardStatusBar.setAlpha(newAlpha);
-        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing ? VISIBLE : INVISIBLE);
+        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+                || mDelayShowingKeyguardStatusBar;
+        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
+                ? VISIBLE : INVISIBLE);
     }
 
     private void updateKeyguardBottomAreaAlpha() {
@@ -2399,6 +2527,7 @@
         mKeyguardStatusBar.setListening(listening);
         if (mQs == null) return;
         mQs.setListening(listening);
+        mNPVPluginManager.setListening(listening);
     }
 
     @Override
@@ -2427,7 +2556,7 @@
 
     @Override
     protected void onTrackingStarted() {
-        mFalsingManager.onTrackingStarted(mStatusBar.isKeyguardCurrentlySecure());
+        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
@@ -2717,7 +2846,7 @@
     @Override
     protected boolean shouldUseDismissingAnimation() {
         return mBarState != StatusBarState.SHADE
-                && (!mStatusBar.isKeyguardCurrentlySecure() || !isTracking());
+                && (mKeyguardStateController.canDismissLockScreen() || !isTracking());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 31600e3..ffaf3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -49,7 +49,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -143,7 +143,8 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
-    protected final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    protected final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
     protected final SysuiStatusBarStateController mStatusBarStateController =
             (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
 
@@ -495,7 +496,8 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
-                && !mStatusBar.isBouncerShowing() && !mKeyguardMonitor.isKeyguardFadingAway()) {
+                && !mStatusBar.isBouncerShowing()
+                && !mKeyguardStateController.isKeyguardFadingAway()) {
             long timePassed = SystemClock.uptimeMillis() - mDownTime;
             if (timePassed < ViewConfiguration.getLongPressTimeout()) {
                 // Lets show the user that he can actually expand the panel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index ee43879..294111c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -57,7 +57,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.RotationLockController;
@@ -82,7 +82,7 @@
                 Listener,
                 ZenModeController.Callback,
                 DeviceProvisionedListener,
-                KeyguardMonitor.Callback,
+                KeyguardStateController.Callback,
                 PrivacyItemController.Callback,
                 LocationController.LocationChangeCallback {
     private static final String TAG = "PhoneStatusBarPolicy";
@@ -119,7 +119,7 @@
     private final DataSaverController mDataSaver;
     private final ZenModeController mZenController;
     private final DeviceProvisionedController mProvisionedController;
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     private final LocationController mLocationController;
     private final PrivacyItemController mPrivacyItemController;
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
@@ -152,7 +152,7 @@
         mDataSaver = Dependency.get(DataSaverController.class);
         mZenController = Dependency.get(ZenModeController.class);
         mProvisionedController = Dependency.get(DeviceProvisionedController.class);
-        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mLocationController = Dependency.get(LocationController.class);
         mPrivacyItemController = Dependency.get(PrivacyItemController.class);
         mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
@@ -256,7 +256,7 @@
         mHotspot.addCallback(mHotspotCallback);
         mNextAlarmController.addCallback(mNextAlarmCallback);
         mDataSaver.addCallback(this);
-        mKeyguardMonitor.addCallback(this);
+        mKeyguardStateController.addCallback(this);
         mPrivacyItemController.addCallback(this);
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
@@ -472,8 +472,8 @@
                 boolean isManagedProfile = mUserManager.isManagedProfile(userId);
                 mHandler.post(() -> {
                     final boolean showIcon;
-                    if (isManagedProfile &&
-                            (!mKeyguardMonitor.isShowing() || mKeyguardMonitor.isOccluded())) {
+                    if (isManagedProfile && (!mKeyguardStateController.isShowing()
+                            || mKeyguardStateController.isOccluded())) {
                         showIcon = true;
                         mIconController.setIcon(mSlotManagedProfile,
                                 R.drawable.stat_sys_managed_profile_status,
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 a7262cf..bd9ce3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -47,7 +47,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.stack.ViewState;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -79,23 +79,24 @@
     /**
      * When both scrims have 0 alpha.
      */
-    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    public static final int TRANSPARENT = 0;
     /**
      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
      */
-    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    public static final int SEMI_TRANSPARENT = 1;
     /**
      * When at least 1 scrim is fully opaque (alpha set to 1.)
      */
-    public static final int VISIBILITY_FULLY_OPAQUE = 2;
+    public static final int OPAQUE = 2;
 
-    @IntDef(prefix = { "VISIBILITY_" }, value = {
-            VISIBILITY_FULLY_TRANSPARENT,
-            VISIBILITY_SEMI_TRANSPARENT,
-            VISIBILITY_FULLY_OPAQUE
+    @IntDef(prefix = {"VISIBILITY_"}, value = {
+            TRANSPARENT,
+            SEMI_TRANSPARENT,
+            OPAQUE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ScrimVisibility {}
+    public @interface ScrimVisibility {
+    }
 
     /**
      * Default alpha value for most scrims.
@@ -123,9 +124,12 @@
 
     private ScrimState mState = ScrimState.UNINITIALIZED;
     private final Context mContext;
-    protected final ScrimView mScrimBehind;
+
     protected final ScrimView mScrimInFront;
-    private final UnlockMethodCache mUnlockMethodCache;
+    protected final ScrimView mScrimBehind;
+    protected final ScrimView mScrimForBubble;
+
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
     private final AlarmTimeout mTimeTicker;
@@ -153,10 +157,15 @@
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
-    private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private int mCurrentInFrontTint;
-    private int mCurrentBehindTint;
+
+    private float mInFrontAlpha = NOT_INITIALIZED;
+    private float mBehindAlpha = NOT_INITIALIZED;
+    private float mBubbleAlpha = NOT_INITIALIZED;
+
+    private int mInFrontTint;
+    private int mBehindTint;
+    private int mBubbleTint;
+
     private boolean mWallpaperVisibilityTimedOut;
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
@@ -175,18 +184,21 @@
     private boolean mWakeLockHeld;
     private boolean mKeyguardOccluded;
 
-    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
-            AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
+            AlarmManager alarmManager, KeyguardStateController keyguardStateController) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
+        mScrimForBubble = scrimForBubble;
+
         mScrimStateListener = scrimStateListener;
         mScrimVisibleListener = scrimVisibleListener;
+
         mContext = scrimBehind.getContext();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
-        mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardStateController = keyguardStateController;
+        mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
@@ -198,11 +210,11 @@
         // to make sure that text on top of it is legible.
         mScrimBehindAlpha = mScrimBehindAlphaResValue;
         mDozeParameters = dozeParameters;
-        keyguardMonitor.addCallback(new KeyguardMonitor.Callback() {
+        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
-                setKeyguardFadingAway(keyguardMonitor.isKeyguardFadingAway(),
-                        keyguardMonitor.getKeyguardFadingAwayDuration());
+                setKeyguardFadingAway(keyguardStateController.isKeyguardFadingAway(),
+                        keyguardStateController.getKeyguardFadingAwayDuration());
             }
         });
 
@@ -213,12 +225,13 @@
 
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
-            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters);
             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
         }
 
         mScrimBehind.setDefaultFocusHighlightEnabled(false);
         mScrimInFront.setDefaultFocusHighlightEnabled(false);
+        mScrimForBubble.setDefaultFocusHighlightEnabled(false);
 
         updateScrims();
     }
@@ -257,10 +270,14 @@
         mBlankScreen = state.getBlanksScreen();
         mAnimateChange = state.getAnimateChange();
         mAnimationDuration = state.getAnimationDuration();
-        mCurrentInFrontTint = state.getFrontTint();
-        mCurrentBehindTint = state.getBehindTint();
-        mCurrentInFrontAlpha = state.getFrontAlpha();
-        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        mInFrontTint = state.getFrontTint();
+        mBehindTint = state.getBehindTint();
+        mBubbleTint = state.getBubbleTint();
+
+        mInFrontAlpha = state.getFrontAlpha();
+        mBehindAlpha = state.getBehindAlpha();
+        mBubbleAlpha = state.getBubbleAlpha();
         applyExpansionToAlpha();
 
         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -350,7 +367,7 @@
 
     public void onTrackingStarted() {
         mTracking = true;
-        mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
+        mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
     }
 
     public void onExpandingFinished() {
@@ -393,21 +410,20 @@
         if (mExpansionFraction != fraction) {
             mExpansionFraction = fraction;
 
-            final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
-                    || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
-            if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
+            boolean relevantState = (mState == ScrimState.UNLOCKED
+                    || mState == ScrimState.KEYGUARD
+                    || mState == ScrimState.PULSING
+                    || mState == ScrimState.BUBBLE_EXPANDED);
+            if (!(relevantState && mExpansionAffectsAlpha)) {
                 return;
             }
-
             applyExpansionToAlpha();
-
             if (mUpdatePending) {
                 return;
             }
-
             setOrAdaptCurrentAnimation(mScrimBehind);
             setOrAdaptCurrentAnimation(mScrimInFront);
-
+            setOrAdaptCurrentAnimation(mScrimForBubble);
             dispatchScrimState(mScrimBehind.getViewAlpha());
 
             // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
@@ -421,11 +437,10 @@
     }
 
     private void setOrAdaptCurrentAnimation(View scrim) {
-        if (!isAnimating(scrim)) {
-            updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
-        } else {
+        float alpha = getCurrentScrimAlpha(scrim);
+        if (isAnimating(scrim)) {
+            // Adapt current animation.
             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
-            float alpha = getCurrentScrimAlpha(scrim);
             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
             float relativeDiff = alpha - previousEndValue;
@@ -433,6 +448,9 @@
             scrim.setTag(TAG_START_ALPHA, newStartValue);
             scrim.setTag(TAG_END_ALPHA, alpha);
             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+        } else {
+            // Set animation.
+            updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
         }
     }
 
@@ -441,27 +459,27 @@
             return;
         }
 
-        if (mState == ScrimState.UNLOCKED) {
+        if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
             // Darken scrim as you pull down the shade when unlocked
             float behindFraction = getInterpolatedFraction();
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
+            mInFrontAlpha = 0;
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
             float alphaBehind = mState.getBehindAlpha();
             if (mDarkenWhileDragging) {
-                mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             } else {
-                mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             }
-            mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+            mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     mState.getBehindTint(), interpolatedFract);
         }
     }
@@ -485,13 +503,14 @@
      * device is dozing when the light sensor is on.
      */
     public void setAodFrontScrimAlpha(float alpha) {
-        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
-                && mCurrentInFrontAlpha != alpha) {
-            mCurrentInFrontAlpha = alpha;
+        if (((mState == ScrimState.AOD && mDozeParameters.getAlwaysOn())
+                || mState == ScrimState.PULSING) && mInFrontAlpha != alpha) {
+            mInFrontAlpha = alpha;
             updateScrims();
         }
 
         mState.AOD.setAodFrontScrimAlpha(alpha);
+        mState.PULSING.setAodFrontScrimAlpha(alpha);
     }
 
     /**
@@ -499,10 +518,10 @@
      * away once the display turns on.
      */
     public void prepareForGentleWakeUp() {
-        if (mState == ScrimState.AOD) {
-            mCurrentInFrontAlpha = 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+            mInFrontAlpha = 1f;
+            mInFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
             mAnimateChange = false;
             updateScrims();
             mAnimateChange = true;
@@ -520,8 +539,8 @@
 
         if (mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -543,8 +562,11 @@
             // Only animate scrim color if the scrim view is actually visible
             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
             boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+            boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+
             mScrimInFront.setColors(mColors, animateScrimInFront);
             mScrimBehind.setColors(mColors, animateScrimBehind);
+            mScrimForBubble.setColors(mColors, animateScrimForBubble);
 
             // Calculate minimum scrim opacity for white or black text.
             int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -563,12 +585,11 @@
         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
                 && mKeyguardOccluded;
         if (aodWallpaperTimeout || occludedKeyguard) {
-            mCurrentBehindAlpha = 1;
+            mBehindAlpha = 1;
         }
-
-        setScrimInFrontAlpha(mCurrentInFrontAlpha);
-        setScrimBehindAlpha(mCurrentBehindAlpha);
-
+        setScrimAlpha(mScrimInFront, mInFrontAlpha);
+        setScrimAlpha(mScrimBehind, mBehindAlpha);
+        setScrimAlpha(mScrimForBubble, mBubbleAlpha);
         dispatchScrimsVisible();
     }
 
@@ -579,11 +600,11 @@
     private void dispatchScrimsVisible() {
         final int currentScrimVisibility;
         if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
-            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+            currentScrimVisibility = OPAQUE;
         } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
-            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+            currentScrimVisibility = TRANSPARENT;
         } else {
-            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+            currentScrimVisibility = SEMI_TRANSPARENT;
         }
 
         if (mScrimsVisibility != currentScrimVisibility) {
@@ -600,18 +621,10 @@
             return 0;
         } else {
             // woo, special effects
-            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
+            return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
         }
     }
 
-    private void setScrimBehindAlpha(float alpha) {
-        setScrimAlpha(mScrimBehind, alpha);
-    }
-
-    private void setScrimInFrontAlpha(float alpha) {
-        setScrimAlpha(mScrimInFront, alpha);
-    }
-
     private void setScrimAlpha(ScrimView scrim, float alpha) {
         if (alpha == 0f) {
             scrim.setClickable(false);
@@ -622,17 +635,26 @@
         updateScrim(scrim, alpha);
     }
 
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front_scrim";
+        } else if (scrim == mScrimBehind) {
+            return "back_scrim";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble_scrim";
+        }
+        return "unknown_scrim";
+    }
+
     private void updateScrimColor(View scrim, float alpha, int tint) {
         alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
                     (int) (alpha * 255));
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
                     Color.alpha(tint));
 
             scrimView.setTint(tint);
@@ -689,9 +711,11 @@
 
     private float getCurrentScrimAlpha(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontAlpha;
+            return mInFrontAlpha;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindAlpha;
+            return mBehindAlpha;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleAlpha;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -699,9 +723,11 @@
 
     private int getCurrentScrimTint(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontTint;
+            return mInFrontTint;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindTint;
+            return mBehindTint;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleTint;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -744,8 +770,9 @@
         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
         // At the end of the animation we need to remove the tint.
         if (mState == ScrimState.UNLOCKED) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mInFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
         }
     }
 
@@ -850,6 +877,7 @@
 
     /**
      * Executes a callback after the frame has hit the display.
+     *
      * @param callback What to run.
      */
     @VisibleForTesting
@@ -893,16 +921,35 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(" ScrimController: ");
-        pw.print("  state: "); pw.println(mState);
-        pw.print("  frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
+        pw.print("  state: ");
+        pw.println(mState);
 
-        pw.print("  backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
+        pw.print("  frontScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimInFront.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mInFrontAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
-        pw.print("   mTracking="); pw.println(mTracking);
+        pw.print("  backScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimBehind.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBehindAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimBehind.getTint()));
+
+        pw.print("  bubbleScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimForBubble.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBubbleAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimForBubble.getTint()));
+
+        pw.print("   mTracking=");
+        pw.println(mTracking);
     }
 
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -949,8 +996,8 @@
         // in this case, back-scrim needs to be re-evaluated
         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -971,10 +1018,13 @@
     public interface Callback {
         default void onStart() {
         }
+
         default void onDisplayBlanked() {
         }
+
         default void onFinished() {
         }
+
         default void onCancelled() {
         }
         /** Returns whether to timeout wallpaper or not. */
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 9fdd3b8..7463c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -36,7 +36,6 @@
      * On the lock screen.
      */
     KEYGUARD(0) {
-
         @Override
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
@@ -53,10 +52,13 @@
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
-            mCurrentInFrontAlpha = 0;
+            mFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = 0;
+            mBehindAlpha = mScrimBehindAlphaKeyguard;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -66,8 +68,9 @@
     BOUNCER(1) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0f;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mFrontAlpha = 0f;
+            mBubbleAlpha = 0f;
         }
     },
 
@@ -77,8 +80,9 @@
     BOUNCER_SCRIMMED(2) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBehindAlpha = 0;
+            mBubbleAlpha = 0f;
+            mFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
         }
     },
 
@@ -88,8 +92,9 @@
     BRIGHTNESS_MIRROR(3) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -101,9 +106,16 @@
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             mBlankScreen = mDisplayRequiresBlanking;
-            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+
+            mFrontTint = Color.BLACK;
+            mFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+
+            mBehindTint = Color.BLACK;
+            mBehindAlpha = ScrimController.TRANSPARENT;
+
+            mBubbleTint = Color.TRANSPARENT;
+            mBubbleAlpha = ScrimController.TRANSPARENT;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
             // DisplayPowerManager may blank the screen for us,
             // in this case we just need to set our state.
@@ -127,9 +139,10 @@
     PULSING(5) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontAlpha = 0f;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentInFrontTint = Color.BLACK;
+            mFrontAlpha = mAodFrontScrimAlpha;
+            mBubbleAlpha = 0f;
+            mBehindTint = Color.BLACK;
+            mFrontTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
             mAnimationDuration = mWakeLockScreenSensorActive
                     ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -154,25 +167,33 @@
     UNLOCKED(6) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            // State that UI will sync to.
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
+
             mAnimationDuration = mKeyguardFadingAway
                     ? mKeyguardFadingAwayDuration
                     : StatusBar.FADE_KEYGUARD_DURATION;
+
             mAnimateChange = !mLaunchingAffordanceWithPreview;
 
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+            mBlankScreen = false;
+
             if (previousState == ScrimState.AOD) {
-                // Fade from black to transparent when coming directly from AOD
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
-                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Set all scrims black, before they fade transparent.
+                updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+
                 // Scrims should still be black at the end of the transition.
-                mCurrentInFrontTint = Color.BLACK;
-                mCurrentBehindTint = Color.BLACK;
+                mFrontTint = Color.BLACK;
+                mBehindTint = Color.BLACK;
+                mBubbleTint = Color.BLACK;
                 mBlankScreen = true;
-            } else {
-                mCurrentInFrontTint = Color.TRANSPARENT;
-                mCurrentBehindTint = Color.TRANSPARENT;
-                mBlankScreen = false;
             }
         }
     },
@@ -183,25 +204,36 @@
     BUBBLE_EXPANDED(7) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = ScrimController.TRANSPARENT;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBubbleAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION;
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
             mBlankScreen = false;
         }
     };
 
     boolean mBlankScreen = false;
     long mAnimationDuration = ScrimController.ANIMATION_DURATION;
-    int mCurrentInFrontTint = Color.TRANSPARENT;
-    int mCurrentBehindTint = Color.TRANSPARENT;
+    int mFrontTint = Color.TRANSPARENT;
+    int mBehindTint = Color.TRANSPARENT;
+    int mBubbleTint = Color.TRANSPARENT;
+
     boolean mAnimateChange = true;
-    float mCurrentInFrontAlpha;
-    float mCurrentBehindAlpha;
     float mAodFrontScrimAlpha;
+    float mFrontAlpha;
+    float mBehindAlpha;
+    float mBubbleAlpha;
+
     float mScrimBehindAlphaKeyguard;
     ScrimView mScrimInFront;
     ScrimView mScrimBehind;
+    ScrimView mScrimForBubble;
+
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mWallpaperSupportsAmbientMode;
@@ -216,13 +248,17 @@
         mIndex = index;
     }
 
-    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
+            DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
+        mScrimForBubble = scrimForBubble;
+
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
     }
 
+    /** Prepare state for transition. */
     public void prepare(ScrimState previousState) {
     }
 
@@ -231,19 +267,27 @@
     }
 
     public float getFrontAlpha() {
-        return mCurrentInFrontAlpha;
+        return mFrontAlpha;
     }
 
     public float getBehindAlpha() {
-        return mCurrentBehindAlpha;
+        return mBehindAlpha;
+    }
+
+    public float getBubbleAlpha() {
+        return mBubbleAlpha;
     }
 
     public int getFrontTint() {
-        return mCurrentInFrontTint;
+        return mFrontTint;
     }
 
     public int getBehindTint() {
-        return mCurrentBehindTint;
+        return mBehindTint;
+    }
+
+    public int getBubbleTint() {
+        return mBubbleTint;
     }
 
     public long getAnimationDuration() {
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 9fc3e47..f06fbbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -212,7 +212,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -222,7 +221,8 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -246,7 +246,7 @@
 import dagger.Subcomponent;
 
 public class StatusBar extends SystemUI implements DemoMode,
-        ActivityStarter, OnUnlockMethodChangedListener,
+        ActivityStarter, KeyguardStateController.Callback,
         OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener,
         StatusBarStateController.StateListener, ShadeController,
@@ -358,7 +358,6 @@
     protected PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected StatusBarWindowController mStatusBarWindowController;
-    protected UnlockMethodCache mUnlockMethodCache;
     @VisibleForTesting
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
@@ -378,6 +377,8 @@
     @Inject
     KeyguardBypassController mKeyguardBypassController;
     @Inject
+    KeyguardStateController mKeyguardStateController;
+    @Inject
     protected HeadsUpManagerPhone mHeadsUpManager;
     @Inject
     DynamicPrivacyController mDynamicPrivacyController;
@@ -545,7 +546,7 @@
                         + "mStatusBarKeyguardViewManager was null");
                 return;
             }
-            if (mKeyguardMonitor.isKeyguardFadingAway()) {
+            if (mKeyguardStateController.isKeyguardFadingAway()) {
                 mStatusBarKeyguardViewManager.onKeyguardFadedAway();
             }
         }
@@ -559,7 +560,6 @@
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     protected UserSwitcherController mUserSwitcherController;
     private NetworkController mNetworkController;
-    private KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
     private BatteryController mBatteryController;
     protected boolean mPanelExpanded;
     private UiModeManager mUiModeManager;
@@ -701,7 +701,7 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -786,8 +786,7 @@
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
         mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
 
-        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
-        mUnlockMethodCache.addListener(this);
+        mKeyguardStateController.addCallback(this);
         startKeyguard();
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
@@ -824,6 +823,7 @@
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mZenController.addCallback(this);
         NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
@@ -944,8 +944,10 @@
 
         ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
         ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+
         mScrimController = SystemUIFactory.getInstance().createScrimController(
-                scrimBehind, scrimInFront, mLockscreenWallpaper,
+                scrimBehind, scrimInFront, scrimForBubble, mLockscreenWallpaper,
                 (state, alpha, color) -> mLightBarController.setScrimState(state, alpha, color),
                 scrimsVisible -> {
                     if (mStatusBarWindowController != null) {
@@ -956,7 +958,7 @@
                     }
                 }, DozeParameters.getInstance(mContext),
                 mContext.getSystemService(AlarmManager.class),
-                mKeyguardMonitor);
+                mKeyguardStateController);
         mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
                 mHeadsUpManager, mNotificationIconAreaController, mScrimController);
         mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context));
@@ -1113,7 +1115,7 @@
                 mHeadsUpManager, activityStarter, mActivityLaunchAnimator,
                 mBarService, mStatusBarStateController, mKeyguardManager, mDreamManager,
                 mRemoteInputManager, mStatusBarRemoteInputCallback, mGroupManager,
-                mLockscreenUserManager, mShadeController, mKeyguardMonitor,
+                mLockscreenUserManager, mShadeController, mKeyguardStateController,
                 mNotificationInterruptionStateProvider, mMetricsLogger,
                 new LockPatternUtils(mContext), Dependency.get(MAIN_HANDLER),
                 Dependency.get(BG_HANDLER), mActivityIntentHelper, mBubbleController);
@@ -1254,13 +1256,13 @@
         KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
         mBiometricUnlockController = new BiometricUnlockController(mContext,
                 mDozeScrimController, keyguardViewMediator,
-                mScrimController, this, UnlockMethodCache.getInstance(mContext),
-                new Handler(), mKeyguardUpdateMonitor, mKeyguardBypassController);
+                mScrimController, this, mKeyguardStateController, new Handler(),
+                mKeyguardUpdateMonitor, mKeyguardBypassController);
         putComponent(BiometricUnlockController.class, mBiometricUnlockController);
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
                 mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
-                mKeyguardBypassController, mFalsingManager);
+                mKeyguardBypassController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1371,7 +1373,7 @@
      * Asks {@link KeyguardUpdateMonitor} to run face auth.
      */
     public void requestFaceAuth() {
-        if (!mUnlockMethodCache.canSkipBouncer()) {
+        if (!mKeyguardStateController.canDismissLockScreen()) {
             mKeyguardUpdateMonitor.requestFaceAuth();
         }
     }
@@ -1555,9 +1557,8 @@
         logStateToEventlog();
     }
 
-    @Override  // UnlockMethodCache.OnUnlockMethodChangedListener
-    public void onUnlockMethodStateChanged() {
-        // Unlock method state changed. Notify KeguardMonitor
+    @Override
+    public void onUnlockedChanged() {
         updateKeyguardState();
         logStateToEventlog();
     }
@@ -1620,10 +1621,6 @@
         }
     }
 
-    public boolean isKeyguardCurrentlySecure() {
-        return !mUnlockMethodCache.canSkipBouncer();
-    }
-
     public void setPanelExpanded(boolean isExpanded) {
         mPanelExpanded = isExpanded;
         updateHideIconsForBouncer(false /* animate */);
@@ -1815,8 +1812,8 @@
     @Override
     public void handleSystemKey(int key) {
         if (SPEW) Log.d(TAG, "handleNavigationKey: " + key);
-        if (!mCommandQueue.panelsEnabled() || !mKeyguardMonitor.isDeviceInteractive()
-                || mKeyguardMonitor.isShowing() && !mKeyguardMonitor.isOccluded()) {
+        if (!mCommandQueue.panelsEnabled() || !mKeyguardUpdateMonitor.isDeviceInteractive()
+                || mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded()) {
             return;
         }
 
@@ -2417,12 +2414,12 @@
             pw.println("  mGroupManager: null");
         }
 
-        if (mLightBarController != null) {
-            mLightBarController.dump(fd, pw, args);
+        if (mBubbleController != null) {
+            mBubbleController.dump(fd, pw, args);
         }
 
-        if (mUnlockMethodCache != null) {
-            mUnlockMethodCache.dump(pw);
+        if (mLightBarController != null) {
+            mLightBarController.dump(fd, pw, args);
         }
 
         if (mKeyguardBypassController != null) {
@@ -2677,7 +2674,7 @@
     public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
         if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
-                && mUnlockMethodCache.canSkipBouncer()
+                && mKeyguardStateController.canDismissLockScreen()
                 && !mStatusBarStateController.leaveOpenOnKeyguardHide()
                 && isPulsing()) {
             // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a pulse.
@@ -2820,14 +2817,14 @@
         boolean isShowing = mStatusBarKeyguardViewManager.isShowing();
         boolean isOccluded = mStatusBarKeyguardViewManager.isOccluded();
         boolean isBouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
-        boolean isSecure = mUnlockMethodCache.isMethodSecure();
-        boolean canSkipBouncer = mUnlockMethodCache.canSkipBouncer();
+        boolean isSecure = mKeyguardStateController.isMethodSecure();
+        boolean unlocked = mKeyguardStateController.canDismissLockScreen();
         int stateFingerprint = getLoggingFingerprint(mState,
                 isShowing,
                 isOccluded,
                 isBouncerShowing,
                 isSecure,
-                canSkipBouncer);
+                unlocked);
         if (stateFingerprint != mLastLoggedStateFingerprint) {
             if (mStatusBarStateLog == null) {
                 mStatusBarStateLog = new LogMaker(MetricsEvent.VIEW_UNKNOWN);
@@ -2841,7 +2838,7 @@
                     isOccluded ? 1 : 0,
                     isBouncerShowing ? 1 : 0,
                     isSecure ? 1 : 0,
-                    canSkipBouncer ? 1 : 0);
+                    unlocked ? 1 : 0);
             mLastLoggedStateFingerprint = stateFingerprint;
         }
     }
@@ -3041,7 +3038,7 @@
 
     public void showKeyguardImpl() {
         mIsKeyguard = true;
-        if (mKeyguardMonitor.isLaunchTransitionFadingAway()) {
+        if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
             mNotificationPanel.animate().cancel();
             onLaunchTransitionFadingEnded();
         }
@@ -3073,7 +3070,7 @@
         mNotificationPanel.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
-        mKeyguardMonitor.setLaunchTransitionFadingAway(false);
+        mKeyguardStateController.setLaunchTransitionFadingAway(false);
         mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
@@ -3098,7 +3095,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
         Runnable hideRunnable = () -> {
-            mKeyguardMonitor.setLaunchTransitionFadingAway(true);
+            mKeyguardStateController.setLaunchTransitionFadingAway(true);
             if (beforeFading != null) {
                 beforeFading.run();
             }
@@ -3191,7 +3188,7 @@
             if (!mStatusBarStateController.isKeyguardRequested()) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             }
-            long delay = mKeyguardMonitor.calculateGoingToFullShadeDelay();
+            long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
             mNotificationPanel.animateToFullShade(delay);
             if (mDraggedDownEntry != null) {
                 mDraggedDownEntry.setUserLocked(false);
@@ -3233,7 +3230,7 @@
     public void keyguardGoingAway() {
         // Treat Keyguard exit animation as an app transition to achieve nice transition for status
         // bar.
-        mKeyguardMonitor.notifyKeyguardGoingAway(true);
+        mKeyguardStateController.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(mDisplayId, true /* forced */);
     }
 
@@ -3253,14 +3250,14 @@
         mCommandQueue.appTransitionStarting(mDisplayId,
                     startTime - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                     LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
-        mKeyguardMonitor.notifyKeyguardFadingAway(delay, fadeoutDuration, isBypassFading);
+        mKeyguardStateController.notifyKeyguardFadingAway(delay, fadeoutDuration, isBypassFading);
     }
 
     /**
      * Notifies that the Keyguard fading away animation is done.
      */
     public void finishKeyguardFadingAway() {
-        mKeyguardMonitor.notifyKeyguardDoneFading();
+        mKeyguardStateController.notifyKeyguardDoneFading();
         mScrimController.setExpansionAffectsAlpha(true);
     }
 
@@ -3506,8 +3503,8 @@
     }
 
     private void updateKeyguardState() {
-        mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
-                mUnlockMethodCache.isMethodSecure(),
+        mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
+                mKeyguardStateController.isMethodSecure(),
                 mStatusBarKeyguardViewManager.isOccluded());
     }
 
@@ -3555,7 +3552,7 @@
 
     public void onTrackingStopped(boolean expand) {
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            if (!expand && !mUnlockMethodCache.canSkipBouncer()) {
+            if (!expand && !mKeyguardStateController.canDismissLockScreen()) {
                 showBouncer(false /* scrimmed */);
             }
         }
@@ -3779,7 +3776,7 @@
 
     @Override
     public void showScreenPinningRequest(int taskId) {
-        if (mKeyguardMonitor.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             // Don't allow apps to trigger this from keyguard.
             return;
         }
@@ -3902,7 +3899,7 @@
         // We don't want to end up in KEYGUARD state when we're unlocking with
         // fingerprint from doze. We should cross fade directly from black.
         boolean unlocking = mBiometricUnlockController.isWakeAndUnlock()
-                || mKeyguardMonitor.isKeyguardFadingAway();
+                || mKeyguardStateController.isKeyguardFadingAway();
 
         // Do not animate the scrim expansion when triggered by the fingerprint sensor.
         mScrimController.setExpansionAffectsAlpha(
@@ -4532,6 +4529,14 @@
     }
 
     /**
+     * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
+     */
+    public void onBouncerPreHideAnimation() {
+        mNotificationPanel.onBouncerPreHideAnimation();
+        mStatusBarWindow.onBouncerPreHideAnimation();
+    }
+
+    /**
      * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
      *         return PackageManager for mContext
      */
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 ccb85fa..df23f8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -56,8 +56,7 @@
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -165,8 +164,8 @@
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
-    private final KeyguardMonitorImpl mKeyguardMonitor =
-            (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
+    private final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
     private final NotificationMediaManager mMediaManager =
             Dependency.get(NotificationMediaManager.class);
     private final SysuiStatusBarStateController mStatusBarStateController =
@@ -193,7 +192,7 @@
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
         Dependency.get(ConfigurationController.class).addCallback(this);
         mGesturalNav = QuickStepContract.isGesturalMode(
@@ -221,7 +220,7 @@
         mBiometricUnlockController = biometricUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
-                mExpansionCallback, falsingManager, bypassController);
+                mExpansionCallback, mKeyguardStateController, falsingManager, bypassController);
         mNotificationPanelView = notificationPanelView;
         notificationPanelView.addExpansionListener(this);
         mBypassController = bypassController;
@@ -245,7 +244,7 @@
                 mBouncer.setExpansion(expansion);
             }
             if (expansion != KeyguardBouncer.EXPANSION_HIDDEN && tracking
-                    && mStatusBar.isKeyguardCurrentlySecure()
+                    && !mKeyguardStateController.canDismissLockScreen()
                     && !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
                 mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
             }
@@ -269,7 +268,7 @@
         boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
                 && !mNotificationPanelView.isQsExpanded();
         boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
-                && !mBouncer.isAnimatingAway() && !mKeyguardMonitor.isKeyguardFadingAway();
+                && !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
 
         if (mLastLockVisible != lockVisible) {
             mLastLockVisible = lockVisible;
@@ -279,14 +278,15 @@
                         0 /* delay */);
             } else {
                 final long duration;
+                final int delay;
                 if (needsBypassFading()) {
                     duration = KeyguardBypassController.BYPASS_PANEL_FADE_DURATION;
+                    delay = 0;
                 } else {
                     duration = AppearAnimationUtils.DEFAULT_APPEAR_DURATION / 2;
+                    delay = 120;
                 }
-                CrossFadeHelper.fadeOut(mLockIconContainer,
-                        duration /* duration */,
-                        0 /* delay */, null /* runnable */);
+                CrossFadeHelper.fadeOut(mLockIconContainer, duration, delay, null /* runnable */);
             }
         }
     }
@@ -298,8 +298,9 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowController.setKeyguardShowing(true);
-        mKeyguardMonitor.notifyKeyguardState(
-                mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
+        mKeyguardStateController.notifyKeyguardState(
+                mShowing, mKeyguardStateController.isMethodSecure(),
+                mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
             StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -395,7 +396,7 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
             }
-            KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
+            Dependency.get(KeyguardUpdateMonitor.class).sendKeyguardReset();
             updateStates();
         }
     }
@@ -516,7 +517,7 @@
     public void startPreHideAnimation(Runnable finishRunnable) {
         if (mBouncer.isShowing()) {
             mBouncer.startPreHideAnimation(finishRunnable);
-            mNotificationPanelView.onBouncerPreHideAnimation();
+            mStatusBar.onBouncerPreHideAnimation();
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
@@ -529,11 +530,11 @@
      */
     public void hide(long startTime, long fadeoutDuration) {
         mShowing = false;
-        mKeyguardMonitor.notifyKeyguardState(
-                mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
+        mKeyguardStateController.notifyKeyguardState(mShowing,
+                mKeyguardStateController.isMethodSecure(), mKeyguardStateController.isOccluded());
         launchPendingWakeupAction();
 
-        if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
+        if (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
             fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
         }
         long uptimeMillis = SystemClock.uptimeMillis();
@@ -738,8 +739,8 @@
     }
 
     private long getNavBarShowDelay() {
-        if (mKeyguardMonitor.isKeyguardFadingAway()) {
-            return mKeyguardMonitor.getKeyguardFadingAwayDelay();
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            return mKeyguardStateController.getKeyguardFadingAwayDelay();
         } else if (mBouncer.isShowing()) {
             return NAV_BAR_SHOW_DELAY_BOUNCER;
         } else {
@@ -785,7 +786,7 @@
             updateLockIcon();
         }
 
-        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
             updateMonitor.onKeyguardVisibilityChanged(showing && !occluded);
         }
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 bec53a1..dfec195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -68,7 +68,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 /**
  * Status bar implementation of {@link NotificationActivityStarter}.
@@ -84,7 +84,7 @@
     private final NotificationRemoteInputManager mRemoteInputManager;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final ShadeController mShadeController;
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     private final ActivityStarter mActivityStarter;
     private final NotificationEntryManager mEntryManager;
     private final StatusBarStateController mStatusBarStateController;
@@ -125,7 +125,7 @@
             NotificationGroupManager groupManager,
             NotificationLockscreenUserManager lockscreenUserManager,
             ShadeController shadeController,
-            KeyguardMonitor keyguardMonitor,
+            KeyguardStateController keyguardStateController,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
@@ -145,7 +145,7 @@
         mRemoteInputManager = remoteInputManager;
         mLockscreenUserManager = lockscreenUserManager;
         mShadeController = shadeController;
-        mKeyguardMonitor = keyguardMonitor;
+        mKeyguardStateController = keyguardStateController;
         mActivityStarter = activityStarter;
         mEntryManager = entryManager;
         mStatusBarStateController = statusBarStateController;
@@ -204,7 +204,7 @@
                 && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
                 mLockscreenUserManager.getCurrentUserId());
         final boolean wasOccluded = mShadeController.isOccluded();
-        boolean showOverLockscreen = mKeyguardMonitor.isShowing() && intent != null
+        boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
                 && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(),
                 mLockscreenUserManager.getCurrentUserId());
         ActivityStarter.OnDismissAction postKeyguardAction =
@@ -258,7 +258,7 @@
         if (showOverLockscreen) {
             mShadeController.addPostCollapseAction(runnable);
             mShadeController.collapsePanel(true /* animate */);
-        } else if (mKeyguardMonitor.isShowing()
+        } else if (mKeyguardStateController.isShowing()
                 && mShadeController.isOccluded()) {
             mShadeController.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapsePanel();
@@ -411,7 +411,7 @@
                 if (DEBUG) {
                     Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.key);
                 }
-            } else if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
+            } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
                 if (DEBUG) {
                     Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.key);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a870590..3e0c268 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -74,7 +74,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.util.ArrayList;
 
@@ -89,7 +89,8 @@
 
     private final ShadeController mShadeController = Dependency.get(ShadeController.class);
     private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
-    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
     private final NotificationViewHierarchyManager mViewHierarchyManager =
             Dependency.get(NotificationViewHierarchyManager.class);
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -123,7 +124,6 @@
     private final DynamicPrivacyController mDynamicPrivacyController;
     private boolean mReinflateNotificationsOnUserSwitched;
     private boolean mDispatchUiModeChangeOnUserSwitched;
-    private final UnlockMethodCache mUnlockMethodCache;
     private TextView mNotificationPanelDebugText;
 
     protected boolean mVrMode;
@@ -152,7 +152,6 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDozeScrimController = dozeScrimController;
         mScrimController = scrimController;
-        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mMaxAllowedKeyguardNotifications = context.getResources().getInteger(
                 R.integer.keyguard_max_notification_count);
@@ -224,7 +223,7 @@
             mVisualStabilityManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(this,
                     notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
-            // ForegroundServiceControllerListener adds its listener in its constructor
+            // ForegroundServiceNotificationListener adds its listener in its constructor
             // but we need to request it here in order for it to be instantiated.
             // TODO: figure out how to do this correctly once Dependency.get() is gone.
             Dependency.get(ForegroundServiceNotificationListener.class);
@@ -240,7 +239,7 @@
     public void onDensityOrFontScaleChanged() {
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+        if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
             updateNotificationsOnDensityOrFontScaleChanged();
         } else {
             mReinflateNotificationsOnUserSwitched = true;
@@ -249,7 +248,7 @@
 
     @Override
     public void onUiModeChanged() {
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+        if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
             updateNotificationOnUiModeChanged();
         } else {
             mDispatchUiModeChangeOnUserSwitched = true;
@@ -366,7 +365,7 @@
                 return false;
             } else {
                 // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent
-                return !mKeyguardMonitor.isShowing()
+                return !mKeyguardStateController.isShowing()
                         || mShadeController.isOccluded();
             }
         }
@@ -398,7 +397,7 @@
     public void onBindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row) {
         row.setAboveShelfChangedListener(mAboveShelfObserver);
-        row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
+        row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 13d4b8e..9a281ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -47,8 +47,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -59,7 +58,8 @@
 public class StatusBarRemoteInputCallback implements Callback, Callbacks,
         StatusBarStateController.StateListener {
 
-    private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+    private final KeyguardStateController mKeyguardStateController = Dependency.get(
+            KeyguardStateController.class);
     private final SysuiStatusBarStateController mStatusBarStateController =
             (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -165,7 +165,7 @@
     @Override
     public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
             View clickedView) {
-        if (mKeyguardMonitor.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             onLockedRemoteInput(row, clickedView);
         } else {
             if (row.isChildInGroup() && !row.areChildrenExpanded()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index e85b147..724b462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -40,9 +40,9 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.R;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -223,7 +223,7 @@
         }
 
         final boolean scrimsOccludingWallpaper =
-                state.scrimsVisibility == ScrimController.VISIBILITY_FULLY_OPAQUE;
+                state.scrimsVisibility == ScrimController.OPAQUE;
         final boolean keyguardOrAod = state.keyguardShowing
                 || (state.dozing && mDozeParameters.getAlwaysOn());
         if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
@@ -309,7 +309,7 @@
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
                 || state.headsUpShowing || state.bubblesShowing
-                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
+                || state.scrimsVisibility != ScrimController.TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 7a81ed4..a9e818d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -527,6 +527,15 @@
         }
     }
 
+    /**
+     * When {@link KeyguardBouncer} starts to be dismissed and starts to play its animation.
+     */
+    public void onBouncerPreHideAnimation() {
+        if (mLockIcon != null) {
+            mLockIcon.onBouncerPreHideAnimation();
+        }
+    }
+
     public class LayoutParams extends FrameLayout.LayoutParams {
 
         public boolean ignoreRightInset;
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 e61a67c..5bda34d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -28,7 +28,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 
 /**
@@ -100,7 +100,7 @@
     }
 
     public static void setWindowOnTop(Dialog dialog) {
-        if (Dependency.get(KeyguardMonitor.class).isShowing()) {
+        if (Dependency.get(KeyguardStateController.class).isShowing()) {
             dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_PANEL);
         } else {
             dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
deleted file mode 100644
index 24ecd14..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Build;
-import android.os.Trace;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.DejankUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Caches whether the current unlock method is insecure, taking trust into account. This information
- * might be a little bit out of date and should not be used for actual security decisions; it should
- * be only used for visual indications.
- */
-public class UnlockMethodCache {
-
-    private static UnlockMethodCache sInstance;
-    private static final boolean DEBUG_AUTH_WITH_ADB = false;
-    private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
-
-    private final LockPatternUtils mLockPatternUtils;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final ArrayList<OnUnlockMethodChangedListener> mListeners = new ArrayList<>();
-    /** Whether the user configured a secure unlock method (PIN, password, etc.) */
-    private boolean mSecure;
-    /** Whether the unlock method is currently insecure (insecure method or trusted environment) */
-    private boolean mCanSkipBouncer;
-    private boolean mTrustManaged;
-    private boolean mTrusted;
-    private boolean mDebugUnlocked = false;
-    private boolean mFaceAuthEnabled;
-
-    private UnlockMethodCache(Context ctx) {
-        mLockPatternUtils = new LockPatternUtils(ctx);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
-        KeyguardUpdateMonitor.getInstance(ctx).registerCallback(mCallback);
-        update(true /* updateAlways */);
-        if (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB) {
-            // Watch for interesting updates
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(AUTH_BROADCAST_KEY);
-            ctx.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (DEBUG_AUTH_WITH_ADB && AUTH_BROADCAST_KEY.equals(intent.getAction())) {
-                        mDebugUnlocked = !mDebugUnlocked;
-                        update(true /* updateAlways */);
-                    }
-                }
-            }, filter, null, null);
-        }
-    }
-
-    public static UnlockMethodCache getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new UnlockMethodCache(context);
-        }
-        return sInstance;
-    }
-
-    /**
-     * @return whether the user configured a secure unlock method like PIN, password, etc.
-     */
-    public boolean isMethodSecure() {
-        return mSecure;
-    }
-
-    public boolean isTrusted() {
-        return mTrusted;
-    }
-
-    /**
-     * @return whether the lockscreen is currently insecure, and the bouncer won't be shown
-     */
-    public boolean canSkipBouncer() {
-        return mCanSkipBouncer;
-    }
-
-    public void addListener(OnUnlockMethodChangedListener listener) {
-        mListeners.add(listener);
-    }
-
-    public void removeListener(OnUnlockMethodChangedListener listener) {
-        mListeners.remove(listener);
-    }
-
-    /**
-     * If there are faces enrolled and user enabled face auth on keyguard.
-     */
-    public boolean isFaceAuthEnabled() {
-        return mFaceAuthEnabled;
-    }
-
-    private void update(boolean updateAlways) {
-        Trace.beginSection("UnlockMethodCache#update");
-        int user = KeyguardUpdateMonitor.getCurrentUser();
-        boolean secure = mLockPatternUtils.isSecure(user);
-        boolean canSkipBouncer = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)
-                || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
-        boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
-        boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
-        boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
-        boolean changed = secure != mSecure || canSkipBouncer != mCanSkipBouncer
-                || trustManaged != mTrustManaged
-                || mFaceAuthEnabled != faceAuthEnabled;
-        if (changed || updateAlways) {
-            mSecure = secure;
-            mCanSkipBouncer = canSkipBouncer;
-            mTrusted = trusted;
-            mTrustManaged = trustManaged;
-            mFaceAuthEnabled = faceAuthEnabled;
-            Trace.endSection();
-            notifyListeners();
-        } else {
-            Trace.endSection();
-        }
-    }
-
-    private void notifyListeners() {
-        String tag = "UnlockMethodCache#notifyListeners";
-        DejankUtils.startDetectingBlockingIpcs(tag);
-        for (OnUnlockMethodChangedListener listener : mListeners) {
-            listener.onUnlockMethodStateChanged();
-        }
-        DejankUtils.stopDetectingBlockingIpcs(tag);
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("UnlockMethodCache");
-        pw.println("  mSecure: " + mSecure);
-        pw.println("  mCanSkipBouncer: " + mCanSkipBouncer);
-        pw.println("  mTrustManaged: " + mTrustManaged);
-        pw.println("  mTrusted: " + mTrusted);
-        pw.println("  mDebugUnlocked: " + mDebugUnlocked);
-        pw.println("  mFaceAuthEnabled: " + mFaceAuthEnabled);
-    }
-
-    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onUserSwitchComplete(int userId) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onTrustChanged(int userId) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onTrustManagedChanged(int userId) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onStartedWakingUp() {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
-            Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
-            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
-                Trace.endSection();
-                return;
-            }
-            update(false /* updateAlways */);
-            Trace.endSection();
-        }
-
-        @Override
-        public void onFaceUnlockStateChanged(boolean running, int userId) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onStrongAuthStateChanged(int userId) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onScreenTurnedOff() {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            update(false /* updateAlways */);
-        }
-
-        @Override
-        public void onBiometricsCleared() {
-            update(false /* alwaysUpdate */);
-        }
-    };
-
-    public boolean isTrustManaged() {
-        return mTrustManaged;
-    }
-
-    public static interface OnUnlockMethodChangedListener {
-        void onUnlockMethodStateChanged();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 40c3d9d..0a2fb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import static com.android.systemui.Dependency.BG_LOOPER_NAME;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -67,16 +68,18 @@
     private boolean mEnabled;
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
 
-    private final H mHandler = new H(Looper.getMainLooper());
+    private final H mHandler;
     private int mState;
 
     /**
      */
     @Inject
     public BluetoothControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
+            @Named(MAIN_LOOPER_NAME) Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager) {
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
+        mHandler = new H(mainLooper);
         if (mLocalBluetoothManager != null) {
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             mLocalBluetoothManager.getProfileManager().addServiceListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index f2c0434..353d6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -32,6 +32,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 
 import java.util.List;
 
@@ -66,7 +67,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.registerCallback(mCallback);
         getContext().registerReceiver(mReceiver,
                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index ee78a72..be27741 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -80,6 +80,7 @@
      */
     public static InflatedSmartReplies inflate(
             Context context,
+            Context packageContext,
             NotificationEntry entry,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
@@ -108,9 +109,9 @@
         }
         if (newSmartRepliesAndActions.smartActions != null) {
             suggestionButtons.addAll(
-                    smartReplyView.inflateSmartActions(newSmartRepliesAndActions.smartActions,
-                            smartReplyController, entry, headsUpManager,
-                            delayOnClickListener));
+                    smartReplyView.inflateSmartActions(packageContext,
+                            newSmartRepliesAndActions.smartActions, smartReplyController, entry,
+                            headsUpManager, delayOnClickListener));
         }
 
         return new InflatedSmartReplies(smartReplyView, suggestionButtons,
@@ -123,7 +124,7 @@
         if (left == right) return true;
         if (left == null || right == null) return false;
 
-        if (!Arrays.equals(left.getSmartReplies(), right.getSmartReplies())) {
+        if (!left.getSmartReplies().equals(right.getSmartReplies())) {
             return false;
         }
 
@@ -199,7 +200,7 @@
         SmartReplyView.SmartActions smartActions = null;
         if (appGeneratedSmartRepliesExist) {
             smartReplies = new SmartReplyView.SmartReplies(
-                    remoteInputActionPair.first.getChoices(),
+                    Arrays.asList(remoteInputActionPair.first.getChoices()),
                     remoteInputActionPair.first,
                     remoteInputActionPair.second.actionIntent,
                     false /* fromAssistant */);
@@ -210,22 +211,22 @@
         }
         // Apps didn't provide any smart replies / actions, use those from NAS (if any).
         if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
-            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.systemGeneratedSmartReplies)
+            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.getSmartReplies())
                     && freeformRemoteInputActionPair != null
                     && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
                     && freeformRemoteInputActionPair.second.actionIntent != null;
             if (useGeneratedReplies) {
                 smartReplies = new SmartReplyView.SmartReplies(
-                        entry.systemGeneratedSmartReplies,
+                        entry.getSmartReplies(),
                         freeformRemoteInputActionPair.first,
                         freeformRemoteInputActionPair.second.actionIntent,
                         true /* fromAssistant */);
             }
-            boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions)
+            boolean useSmartActions = !ArrayUtils.isEmpty(entry.getSmartActions())
                     && notification.getAllowSystemGeneratedContextualActions();
             if (useSmartActions) {
                 List<Notification.Action> systemGeneratedActions =
-                        entry.systemGeneratedSmartActions;
+                        entry.getSmartActions();
                 // Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
                 // since notifications aren't shown there anyway.
                 ActivityManagerWrapper activityManagerWrapper =
@@ -288,8 +289,8 @@
             this.smartActions = smartActions;
         }
 
-        @NonNull public CharSequence[] getSmartReplies() {
-            return smartReplies == null ? new CharSequence[0] : smartReplies.choices;
+        @NonNull public List<CharSequence> getSmartReplies() {
+            return smartReplies == null ? Collections.emptyList() : smartReplies.choices;
         }
 
         @NonNull public List<Notification.Action> getSmartActions() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
deleted file mode 100644
index e1ef809..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ /dev/null
@@ -1,82 +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 com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
-
-public interface KeyguardMonitor extends CallbackController<Callback> {
-
-    boolean isSecure();
-    boolean canSkipBouncer();
-    boolean isShowing();
-    boolean isOccluded();
-    boolean isKeyguardFadingAway();
-    boolean isKeyguardGoingAway();
-    boolean isLaunchTransitionFadingAway();
-    long getKeyguardFadingAwayDuration();
-    long getKeyguardFadingAwayDelay();
-    long calculateGoingToFullShadeDelay();
-
-    /**
-     * @return a shortened fading away duration similar to
-     * {{@link #getKeyguardFadingAwayDuration()}} which may only span half of the duration, unless
-     * we're bypassing
-     */
-    default long getShortenedFadingAwayDuration() {
-        if (isBypassFadingAnimation()) {
-            return getKeyguardFadingAwayDuration();
-        } else {
-            return getKeyguardFadingAwayDuration() / 2;
-        }
-    }
-
-    default boolean isDeviceInteractive() {
-        return false;
-    }
-
-    default void setLaunchTransitionFadingAway(boolean b) {
-    }
-
-    default void notifyKeyguardGoingAway(boolean b) {
-    }
-
-    /**
-     * @return {@code true} if the current fading away animation is the fast bypass fading.
-     */
-    default boolean isBypassFadingAnimation() {
-        return false;
-    }
-
-    /**
-     * Notifies that the Keyguard is fading away with the specified timings.
-     * @param delay the precalculated animation delay in milliseconds
-     * @param fadeoutDuration the duration of the exit animation, in milliseconds
-     * @param isBypassFading is this a fading away animation while bypassing
-     */
-    default void notifyKeyguardFadingAway(long delay, long fadeoutDuration,
-            boolean isBypassFading) {
-    }
-
-    default void notifyKeyguardDoneFading() {
-    }
-
-    default void notifyKeyguardState(boolean showing, boolean methodSecure, boolean occluded) {
-    }
-
-    interface Callback {
-        default void onKeyguardShowingChanged() {}
-        default void onKeyguardFadingAwayChanged() {}
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
deleted file mode 100644
index 87ed14a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.content.Context;
-
-import com.android.internal.util.Preconditions;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
-
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- */
-@Singleton
-public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
-        implements KeyguardMonitor {
-
-    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
-
-    private final Context mContext;
-    private final CurrentUserTracker mUserTracker;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
-    private int mCurrentUser;
-    private boolean mShowing;
-    private boolean mSecure;
-    private boolean mOccluded;
-    private boolean mCanSkipBouncer;
-
-    private boolean mListening;
-    private boolean mKeyguardFadingAway;
-    private long mKeyguardFadingAwayDelay;
-    private long mKeyguardFadingAwayDuration;
-    private boolean mKeyguardGoingAway;
-    private boolean mLaunchTransitionFadingAway;
-    private boolean mBypassFadingAnimation;
-
-    /**
-     */
-    @Inject
-    public KeyguardMonitorImpl(Context context) {
-        mContext = context;
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mUserTracker = new CurrentUserTracker(mContext) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
-                updateCanSkipBouncerState();
-            }
-        };
-    }
-
-    @Override
-    public void addCallback(@NonNull Callback callback) {
-        Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
-        mCallbacks.add(callback);
-        if (mCallbacks.size() != 0 && !mListening) {
-            mListening = true;
-            mCurrentUser = ActivityManager.getCurrentUser();
-            updateCanSkipBouncerState();
-            mKeyguardUpdateMonitor.registerCallback(this);
-            mUserTracker.startTracking();
-        }
-    }
-
-    @Override
-    public void removeCallback(@NonNull Callback callback) {
-        Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
-            mListening = false;
-            mKeyguardUpdateMonitor.removeCallback(this);
-            mUserTracker.stopTracking();
-        }
-    }
-
-    @Override
-    public boolean isShowing() {
-        return mShowing;
-    }
-
-    @Override
-    public boolean isSecure() {
-        return mSecure;
-    }
-
-    @Override
-    public boolean isOccluded() {
-        return mOccluded;
-    }
-
-    @Override
-    public boolean canSkipBouncer() {
-        return mCanSkipBouncer;
-    }
-
-    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
-        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
-        mShowing = showing;
-        mSecure = secure;
-        mOccluded = occluded;
-        notifyKeyguardChanged();
-    }
-
-    @Override
-    public void onTrustChanged(int userId) {
-        updateCanSkipBouncerState();
-        notifyKeyguardChanged();
-    }
-
-    public boolean isDeviceInteractive() {
-        return mKeyguardUpdateMonitor.isDeviceInteractive();
-    }
-
-    private void updateCanSkipBouncerState() {
-        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
-    }
-
-    private void notifyKeyguardChanged() {
-        // Copy the list to allow removal during callback.
-        new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
-    }
-
-    public void notifyKeyguardFadingAway(long delay, long fadeoutDuration, boolean isBypassFading) {
-        mKeyguardFadingAwayDelay = delay;
-        mKeyguardFadingAwayDuration = fadeoutDuration;
-        mBypassFadingAnimation = isBypassFading;
-        setKeyguardFadingAway(true);
-    }
-
-    private void setKeyguardFadingAway(boolean keyguardFadingAway) {
-        if (mKeyguardFadingAway != keyguardFadingAway) {
-            mKeyguardFadingAway = keyguardFadingAway;
-            ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
-            for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).onKeyguardFadingAwayChanged();
-            }
-        }
-    }
-
-    public void notifyKeyguardDoneFading() {
-        mKeyguardGoingAway = false;
-        setKeyguardFadingAway(false);
-    }
-
-    @Override
-    public boolean isKeyguardFadingAway() {
-        return mKeyguardFadingAway;
-    }
-
-    @Override
-    public boolean isKeyguardGoingAway() {
-        return mKeyguardGoingAway;
-    }
-
-    @Override
-    public boolean isBypassFadingAnimation() {
-        return mBypassFadingAnimation;
-    }
-
-    @Override
-    public long getKeyguardFadingAwayDelay() {
-        return mKeyguardFadingAwayDelay;
-    }
-
-    @Override
-    public long getKeyguardFadingAwayDuration() {
-        return mKeyguardFadingAwayDuration;
-    }
-
-    @Override
-    public long calculateGoingToFullShadeDelay() {
-        return mKeyguardFadingAwayDelay + mKeyguardFadingAwayDuration;
-    }
-
-    public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
-        mKeyguardGoingAway = keyguardGoingAway;
-    }
-
-    public void setLaunchTransitionFadingAway(boolean fadingAway) {
-        mLaunchTransitionFadingAway = fadingAway;
-    }
-
-    @Override
-    public boolean isLaunchTransitionFadingAway() {
-        return mLaunchTransitionFadingAway;
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
new file mode 100644
index 0000000..aefe201
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -0,0 +1,177 @@
+/*
+ * 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 com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
+
+/**
+ * Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
+ */
+public interface KeyguardStateController extends CallbackController<Callback> {
+
+    /**
+     * If the device is locked or unlocked.
+     */
+    default boolean isUnlocked() {
+        return !isShowing() || canDismissLockScreen();
+    }
+
+    /**
+     * If the lock screen is visible.
+     * The keyguard is also visible when the device is asleep or in always on mode, except when
+     * the screen timed out and the user can unlock by quickly pressing power.
+     *
+     * This is unrelated to being locked or not.
+     *
+     * @see #isUnlocked()
+     * @see #canDismissLockScreen()
+     */
+    boolean isShowing();
+
+    /**
+     * If swiping up will unlock without asking for a password.
+     * @see #isUnlocked()
+     */
+    boolean canDismissLockScreen();
+
+    /**
+     * If the device has PIN/pattern/password or a lock screen at all.
+     */
+    boolean isMethodSecure();
+
+    /**
+     * When there's an {@link android.app.Activity} on top of the keyguard, where
+     * {@link android.app.Activity#setShowWhenLocked(boolean)} is true.
+     */
+    boolean isOccluded();
+
+    /**
+     * If a {@link android.service.trust.TrustAgentService} is keeping the device unlocked.
+     * {@link #canDismissLockScreen()} is better source of truth that also considers this state.
+     */
+    boolean isTrusted();
+
+    /**
+     * If the keyguard dismissal animation is running.
+     * @see #isKeyguardGoingAway()
+     */
+    boolean isKeyguardFadingAway();
+
+    /**
+     * When the keyguard challenge was successfully solved, and {@link android.app.ActivityManager}
+     * is launching the activity that will be revealed.
+     *
+     * This also includes the animation of the keyguard being dismissed, meaning that this will
+     * return {@code true} whenever {@link #isKeyguardFadingAway()} also returns {@code true}.
+     */
+    boolean isKeyguardGoingAway();
+
+    /**
+     * @return a shortened fading away duration similar to
+     * {{@link #getKeyguardFadingAwayDuration()}} which may only span half of the duration, unless
+     * we're bypassing
+     */
+    default long getShortenedFadingAwayDuration() {
+        if (isBypassFadingAnimation()) {
+            return getKeyguardFadingAwayDuration();
+        } else {
+            return getKeyguardFadingAwayDuration() / 2;
+        }
+    }
+
+    /**
+     * @return {@code true} if the current fading away animation is the fast bypass fading.
+     */
+    default boolean isBypassFadingAnimation() {
+        return false;
+    }
+
+    /**
+     * Notifies that the Keyguard is fading away with the specified timings.
+     * @param delay the precalculated animation delay in milliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
+     * @param isBypassFading is this a fading away animation while bypassing
+     */
+    default void notifyKeyguardFadingAway(long delay, long fadeoutDuration,
+            boolean isBypassFading) {
+    }
+
+    /**
+     * If there are faces enrolled and user enabled face auth on keyguard.
+     */
+    default boolean isFaceAuthEnabled() {
+        return false;
+    }
+
+    /**
+     * If the animation that morphs a notification into an app window is playing.
+     */
+    boolean isLaunchTransitionFadingAway();
+
+    /**
+     * How long the keyguard dismissal animation should take when unlocking.
+     */
+    long getKeyguardFadingAwayDuration();
+
+    /**
+     * Delay for {@link #getKeyguardFadingAwayDuration()}.
+     */
+    long getKeyguardFadingAwayDelay();
+
+    /**
+     * Delay when going from {@link StatusBarState#KEYGUARD} to {@link StatusBarState#SHADE} or
+     * {@link StatusBarState#SHADE_LOCKED}.
+     */
+    long calculateGoingToFullShadeDelay();
+
+    /** **/
+    default void setLaunchTransitionFadingAway(boolean b) {}
+    /** **/
+    default void notifyKeyguardGoingAway(boolean b) {}
+    /** **/
+    default void notifyKeyguardDoneFading() {}
+    /** **/
+    default void notifyKeyguardState(boolean showing, boolean methodSecure, boolean occluded) {}
+
+    /**
+     * Callback for authentication events.
+     */
+    interface Callback {
+        /**
+         * Called when the locked state of the device changes. The lock screen might still be
+         * showing on some cases, like when a {@link android.service.trust.TrustAgentService} is
+         * active, or face auth was triggered but the user didn't swipe up to dismiss the lock
+         * screen yet.
+         */
+        default void onUnlockedChanged() {}
+
+        /**
+         * If the lock screen is active or not. This is different from being locked, since the lock
+         * screen can be visible but unlocked by {@link android.service.trust.TrustAgentService} or
+         * face unlock.
+         *
+         * @see #isShowing()
+         */
+        default void onKeyguardShowingChanged() {}
+
+        /**
+         * Triggered when the device was just unlocked and the lock screen is being dismissed.
+         */
+        default void onKeyguardFadingAwayChanged() {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
new file mode 100644
index 0000000..f8c7532
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.Build;
+import android.os.Trace;
+
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ */
+@Singleton
+public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback
+        implements KeyguardStateController, Dumpable {
+
+    private static final boolean DEBUG_AUTH_WITH_ADB = false;
+    private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
+
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final Context mContext;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final LockPatternUtils mLockPatternUtils;
+    private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+            new LockedStateInvalidator();
+
+    private boolean mCanDismissLockScreen;
+    private boolean mShowing;
+    private boolean mSecure;
+    private boolean mOccluded;
+
+    private boolean mListening;
+    private boolean mKeyguardFadingAway;
+    private long mKeyguardFadingAwayDelay;
+    private long mKeyguardFadingAwayDuration;
+    private boolean mKeyguardGoingAway;
+    private boolean mLaunchTransitionFadingAway;
+    private boolean mBypassFadingAnimation;
+    private boolean mTrustManaged;
+    private boolean mTrusted;
+    private boolean mDebugUnlocked = false;
+    private boolean mFaceAuthEnabled;
+
+    /**
+     */
+    @Inject
+    public KeyguardStateControllerImpl(Context context) {
+        mContext = context;
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mLockPatternUtils = new LockPatternUtils(context);
+        mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+
+        update(true /* updateAlways */);
+        if (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB) {
+            // Watch for interesting updates
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(AUTH_BROADCAST_KEY);
+            context.registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (DEBUG_AUTH_WITH_ADB && AUTH_BROADCAST_KEY.equals(intent.getAction())) {
+                        mDebugUnlocked = !mDebugUnlocked;
+                        update(true /* updateAlways */);
+                    }
+                }
+            }, filter, null, null);
+        }
+    }
+
+    @Override
+    public void addCallback(@NonNull Callback callback) {
+        Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
+        mCallbacks.add(callback);
+        if (mCallbacks.size() != 0 && !mListening) {
+            mListening = true;
+            mKeyguardUpdateMonitor.registerCallback(this);
+        }
+    }
+
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
+        Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
+        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
+            mListening = false;
+            mKeyguardUpdateMonitor.removeCallback(this);
+        }
+    }
+
+    @Override
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    @Override
+    public boolean isMethodSecure() {
+        return mSecure;
+    }
+
+    @Override
+    public boolean isOccluded() {
+        return mOccluded;
+    }
+
+    @Override
+    public boolean isTrusted() {
+        return mTrusted;
+    }
+
+    @Override
+    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
+        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+        mShowing = showing;
+        mSecure = secure;
+        mOccluded = occluded;
+        notifyKeyguardChanged();
+    }
+
+    @Override
+    public void onTrustChanged(int userId) {
+        notifyKeyguardChanged();
+    }
+
+    private void notifyKeyguardChanged() {
+        // Copy the list to allow removal during callback.
+        new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
+    }
+
+    private void notifyUnlockedChanged() {
+        // Copy the list to allow removal during callback.
+        new ArrayList<>(mCallbacks).forEach(Callback::onUnlockedChanged);
+    }
+
+    @Override
+    public void notifyKeyguardFadingAway(long delay, long fadeoutDuration, boolean isBypassFading) {
+        mKeyguardFadingAwayDelay = delay;
+        mKeyguardFadingAwayDuration = fadeoutDuration;
+        mBypassFadingAnimation = isBypassFading;
+        setKeyguardFadingAway(true);
+    }
+
+    private void setKeyguardFadingAway(boolean keyguardFadingAway) {
+        if (mKeyguardFadingAway != keyguardFadingAway) {
+            mKeyguardFadingAway = keyguardFadingAway;
+            ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
+            for (int i = 0; i < callbacks.size(); i++) {
+                callbacks.get(i).onKeyguardFadingAwayChanged();
+            }
+        }
+    }
+
+    @Override
+    public void notifyKeyguardDoneFading() {
+        mKeyguardGoingAway = false;
+        setKeyguardFadingAway(false);
+    }
+
+    private void update(boolean updateAlways) {
+        Trace.beginSection("KeyguardStateController#update");
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        boolean secure = mLockPatternUtils.isSecure(user);
+        boolean canDismissLockScreen = !secure || mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)
+                || (Build.IS_DEBUGGABLE && DEBUG_AUTH_WITH_ADB && mDebugUnlocked);
+        boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
+        boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
+        boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
+        boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
+                || trustManaged != mTrustManaged
+                || mFaceAuthEnabled != faceAuthEnabled;
+        if (changed || updateAlways) {
+            mSecure = secure;
+            mCanDismissLockScreen = canDismissLockScreen;
+            mTrusted = trusted;
+            mTrustManaged = trustManaged;
+            mFaceAuthEnabled = faceAuthEnabled;
+            notifyUnlockedChanged();
+        }
+        Trace.endSection();
+    }
+
+    @Override
+    public boolean canDismissLockScreen() {
+        return mCanDismissLockScreen;
+    }
+
+    @Override
+    public boolean isFaceAuthEnabled() {
+        return mFaceAuthEnabled;
+    }
+
+    @Override
+    public boolean isKeyguardFadingAway() {
+        return mKeyguardFadingAway;
+    }
+
+    @Override
+    public boolean isKeyguardGoingAway() {
+        return mKeyguardGoingAway;
+    }
+
+    @Override
+    public boolean isBypassFadingAnimation() {
+        return mBypassFadingAnimation;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDelay() {
+        return mKeyguardFadingAwayDelay;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDuration() {
+        return mKeyguardFadingAwayDuration;
+    }
+
+    @Override
+    public long calculateGoingToFullShadeDelay() {
+        return mKeyguardFadingAwayDelay + mKeyguardFadingAwayDuration;
+    }
+
+    @Override
+    public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
+        mKeyguardGoingAway = keyguardGoingAway;
+    }
+
+    @Override
+    public void setLaunchTransitionFadingAway(boolean fadingAway) {
+        mLaunchTransitionFadingAway = fadingAway;
+    }
+
+    @Override
+    public boolean isLaunchTransitionFadingAway() {
+        return mLaunchTransitionFadingAway;
+    }
+
+    /**
+     * Dumps internal state for debugging.
+     * @param pw Where to dump.
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("KeyguardStateController:");
+        pw.println("  mSecure: " + mSecure);
+        pw.println("  mCanDismissLockScreen: " + mCanDismissLockScreen);
+        pw.println("  mTrustManaged: " + mTrustManaged);
+        pw.println("  mTrusted: " + mTrusted);
+        pw.println("  mDebugUnlocked: " + mDebugUnlocked);
+        pw.println("  mFaceAuthEnabled: " + mFaceAuthEnabled);
+    }
+
+    private class LockedStateInvalidator extends KeyguardUpdateMonitorCallback {
+        @Override
+        public void onUserSwitchComplete(int userId) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onTrustChanged(int userId) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onTrustManagedChanged(int userId) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onStartedWakingUp() {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+            Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
+            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                Trace.endSection();
+                return;
+            }
+            update(false /* updateAlways */);
+            Trace.endSection();
+        }
+
+        @Override
+        public void onFaceUnlockStateChanged(boolean running, int userId) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onStrongAuthStateChanged(int userId) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onScreenTurnedOff() {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            update(false /* updateAlways */);
+        }
+
+        @Override
+        public void onBiometricsCleared() {
+            update(false /* alwaysUpdate */);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 7acf4fd..5da7267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.provider.Settings.Global;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
@@ -54,6 +55,10 @@
 
 public class MobileSignalController extends SignalController<
         MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
+
+    // The message to display Nr5G icon gracfully by CarrierConfig timeout
+    private static final int MSG_DISPLAY_GRACE = 1;
+
     private final TelephonyManager mPhone;
     private final SubscriptionDefaults mDefaults;
     private final String mNetworkNameDefault;
@@ -76,8 +81,11 @@
     private SignalStrength mSignalStrength;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
+    private final Handler mDisplayGraceHandler;
     @VisibleForTesting
     boolean mInflateSignalStrengths = false;
+    @VisibleForTesting
+    boolean mIsShowingIconGracefully = false;
     // Some specific carriers have 5GE network which is special LTE CA network.
     private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1;
 
@@ -116,6 +124,16 @@
                 updateTelephony();
             }
         };
+
+        mDisplayGraceHandler = new Handler(receiverLooper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_DISPLAY_GRACE) {
+                    mIsShowingIconGracefully = false;
+                    updateTelephony();
+                }
+            }
+        };
     }
 
     public void setConfiguration(Config config) {
@@ -276,7 +294,8 @@
             }
             boolean dataDisabled = mCurrentState.userSetup
                     && (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
-                    || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA);
+                    || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA
+                            && mCurrentState.defaultDataOff));
             boolean noInternet = mCurrentState.inetCondition == 0;
             boolean cutOut = dataDisabled || noInternet;
             return SignalDrawable.getState(level, getNumLevels(), cutOut);
@@ -302,7 +321,7 @@
             dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
         }
         final boolean dataDisabled = (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
-                || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)
+                || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA))
                 && mCurrentState.userSetup;
 
         // Show icon in QS when we are connected or data is disabled.
@@ -466,6 +485,7 @@
             Log.d(mTag, "updateTelephonySignalStrength: hasService=" +
                     Utils.isInService(mServiceState) + " ss=" + mSignalStrength);
         }
+        checkDefaultData();
         mCurrentState.connected = Utils.isInService(mServiceState)
                 && mSignalStrength != null;
         if (mCurrentState.connected) {
@@ -479,6 +499,10 @@
         // When the device is camped on a 5G Non-Standalone network, the data network type is still
         // LTE. In this case, we first check which 5G icon should be shown.
         MobileIconGroup nr5GIconGroup = getNr5GIconGroup();
+        if (mConfig.nrIconDisplayGracePeriodMs > 0) {
+            nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup);
+        }
+
         if (nr5GIconGroup != null) {
             mCurrentState.iconGroup = nr5GIconGroup;
         } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
@@ -519,6 +543,23 @@
         notifyListenersIfNecessary();
     }
 
+    /**
+     * If we are controlling the NOT_DEFAULT_DATA icon, check the status of the other one
+     */
+    private void checkDefaultData() {
+        if (mCurrentState.iconGroup != TelephonyIcons.NOT_DEFAULT_DATA) {
+            mCurrentState.defaultDataOff = false;
+            return;
+        }
+
+        mCurrentState.defaultDataOff = mNetworkController.isDataControllerDisabled();
+    }
+
+    void onMobileDataChanged() {
+        checkDefaultData();
+        notifyListenersIfNecessary();
+    }
+
     private MobileIconGroup getNr5GIconGroup() {
         if (mServiceState == null) return null;
 
@@ -555,7 +596,47 @@
         return null;
     }
 
-    private boolean isDataDisabled() {
+    /**
+     * The function to adjust MobileIconGroup depend on CarrierConfig's time
+     * nextIconGroup == null imply next state could be 2G/3G/4G/4G+
+     * nextIconGroup != null imply next state will be 5G/5G+
+     * Flag : mIsShowingIconGracefully
+     * ---------------------------------------------------------------------------------
+     * |   Last state   |  Current state  | Flag |       Action                        |
+     * ---------------------------------------------------------------------------------
+     * |     5G/5G+     | 2G/3G/4G/4G+    | true | return previous IconGroup           |
+     * |     5G/5G+     |     5G/5G+      | true | Bypass                              |
+     * |  2G/3G/4G/4G+  |     5G/5G+      | true | Bypass                              |
+     * |  2G/3G/4G/4G+  | 2G/3G/4G/4G+    | true | Bypass                              |
+     * |  SS.connected  | SS.disconnect   |  T|F | Reset timer                         |
+     * |NETWORK_TYPE_LTE|!NETWORK_TYPE_LTE|  T|F | Reset timer                         |
+     * |     5G/5G+     | 2G/3G/4G/4G+    | false| Bypass                              |
+     * |     5G/5G+     |     5G/5G+      | false| Bypass                              |
+     * |  2G/3G/4G/4G+  |     5G/5G+      | false| SendMessageDelay(time), flag->true  |
+     * |  2G/3G/4G/4G+  | 2G/3G/4G/4G+    | false| Bypass                              |
+     * ---------------------------------------------------------------------------------
+     */
+    private MobileIconGroup adjustNr5GIconGroupByDisplayGraceTime(
+            MobileIconGroup candidateIconGroup) {
+        if (mIsShowingIconGracefully && candidateIconGroup == null) {
+            candidateIconGroup = (MobileIconGroup) mCurrentState.iconGroup;
+        } else if (!mIsShowingIconGracefully && candidateIconGroup != null
+                && mLastState.iconGroup != candidateIconGroup) {
+            mDisplayGraceHandler.sendMessageDelayed(
+                    mDisplayGraceHandler.obtainMessage(MSG_DISPLAY_GRACE),
+                    mConfig.nrIconDisplayGracePeriodMs);
+            mIsShowingIconGracefully = true;
+        } else if (!mCurrentState.connected || mDataState == TelephonyManager.DATA_DISCONNECTED
+                || candidateIconGroup == null) {
+            mDisplayGraceHandler.removeMessages(MSG_DISPLAY_GRACE);
+            mIsShowingIconGracefully = false;
+            candidateIconGroup = null;
+        }
+
+        return candidateIconGroup;
+    }
+
+    boolean isDataDisabled() {
         return !mPhone.isDataCapable();
     }
 
@@ -580,6 +661,7 @@
         pw.println("  mDataNetType=" + mDataNetType + ",");
         pw.println("  mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
+        pw.println("  mIsShowingIconGracefully=" + mIsShowingIconGracefully + ",");
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
@@ -687,6 +769,7 @@
         boolean isDefault;
         boolean userSetup;
         boolean roaming;
+        boolean defaultDataOff;  // Tracks the on/off state of the defaultDataSubscription
 
         @Override
         public void copyFrom(State s) {
@@ -702,6 +785,7 @@
             carrierNetworkChangeMode = state.carrierNetworkChangeMode;
             userSetup = state.userSetup;
             roaming = state.roaming;
+            defaultDataOff = state.defaultDataOff;
         }
 
         @Override
@@ -718,7 +802,8 @@
             builder.append("airplaneMode=").append(airplaneMode).append(',');
             builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode)
                     .append(',');
-            builder.append("userSetup=").append(userSetup);
+            builder.append("userSetup=").append(userSetup).append(',');
+            builder.append("defaultDataOff=").append(defaultDataOff);
         }
 
         @Override
@@ -733,7 +818,8 @@
                     && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
                     && ((MobileState) o).userSetup == userSetup
                     && ((MobileState) o).isDefault == isDefault
-                    && ((MobileState) o).roaming == roaming;
+                    && ((MobileState) o).roaming == roaming
+                    && ((MobileState) o).defaultDataOff == defaultDataOff;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 292571e..7b5d489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -51,6 +51,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.SparseArray;
@@ -219,6 +220,7 @@
             @Override
             public void onMobileDataEnabled(boolean enabled) {
                 mCallbackHandler.setMobileDataEnabled(enabled);
+                notifyControllersMobileDataChanged();
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
@@ -385,6 +387,22 @@
         return mMobileSignalControllers.size();
     }
 
+    boolean isDataControllerDisabled() {
+        MobileSignalController dataController = getDataController();
+        if (dataController == null) {
+            return false;
+        }
+
+        return dataController.isDataDisabled();
+    }
+
+    private void notifyControllersMobileDataChanged() {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.onMobileDataChanged();
+        }
+    }
+
     public boolean isEmergencyOnly() {
         if (mMobileSignalControllers.size() == 0) {
             // When there are no active subscriptions, determine emengency state from last
@@ -833,6 +851,13 @@
         pw.print("  mEmergencySource=");
         pw.println(emergencyToString(mEmergencySource));
 
+        pw.println("  - config ------");
+        pw.print("  patternOfCarrierSpecificDataIcon=");
+        pw.println(mConfig.patternOfCarrierSpecificDataIcon);
+        pw.print("  nr5GIconMap=");
+        pw.println(mConfig.nr5GIconMap.toString());
+        pw.print("  nrIconDisplayGracePeriodMs=");
+        pw.println(mConfig.nrIconDisplayGracePeriodMs);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
             mobileSignalController.dump(pw);
@@ -996,6 +1021,8 @@
                             datatype.equals("3g") ? TelephonyIcons.THREE_G :
                             datatype.equals("4g") ? TelephonyIcons.FOUR_G :
                             datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
+                            datatype.equals("5g") ? TelephonyIcons.NR_5G :
+                            datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
                             datatype.equals("e") ? TelephonyIcons.E :
                             datatype.equals("g") ? TelephonyIcons.G :
                             datatype.equals("h") ? TelephonyIcons.H :
@@ -1127,6 +1154,7 @@
         boolean inflateSignalStrengths = false;
         boolean alwaysShowDataRatIcon = false;
         public String patternOfCarrierSpecificDataIcon = "";
+        public long nrIconDisplayGracePeriodMs;
 
         /**
          * Mapping from NR 5G status string to an integer. The NR 5G status string should match
@@ -1179,6 +1207,9 @@
                         add5GIconMapping(pair, config);
                     }
                 }
+                setDisplayGraceTime(
+                        b.getInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT),
+                        config);
             }
 
             return config;
@@ -1212,5 +1243,18 @@
                         TelephonyIcons.ICON_NAME_TO_ICON.get(value));
             }
         }
+
+        /**
+         * Set display gracefully period time(MS) depend on carrierConfig KEY
+         * KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, and this function will convert to ms.
+         * {@link CarrierConfigManager}.
+         *
+         * @param time   showing 5G icon gracefully in the period of the time(SECOND)
+         * @param config container that used to store the parsed configs.
+         */
+        @VisibleForTesting
+        static void setDisplayGraceTime(int time, Config config) {
+            config.nrIconDisplayGracePeriodMs = time * DateUtils.SECOND_IN_MILLIS;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index abe3f2c..fc6e5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -18,12 +18,12 @@
 import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
 
 import android.content.Context;
-import android.text.format.DateFormat;
 import android.util.Log;
 
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.BitSet;
 
 
@@ -254,6 +254,8 @@
     }
 
     static class State {
+        // No locale as it's only used for logging purposes
+        private static SimpleDateFormat sSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
         boolean connected;
         boolean enabled;
         boolean activityIn;
@@ -301,7 +303,7 @@
                     .append("activityOut=").append(activityOut).append(',')
                     .append("activityDormant=").append(activityDormant).append(',')
                     .append("rssi=").append(rssi).append(',')
-                    .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time));
+                    .append("lastModified=").append(sSDF.format(time));
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 282d28c..b5f660a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -220,7 +220,7 @@
 
         if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
             if (smartReplies.choices != null) {
-                for (int i = 0; i < smartReplies.choices.length; ++i) {
+                for (int i = 0; i < smartReplies.choices.size(); ++i) {
                     buttons.add(inflateReplyButton(
                             this, getContext(), i, smartReplies, smartReplyController, entry,
                             delayOnClickListener));
@@ -235,17 +235,17 @@
      * Add smart actions to be shown next to smart replies. Only the actions that fit into the
      * notification are shown.
      */
-    public List<Button> inflateSmartActions(@NonNull SmartActions smartActions,
-            SmartReplyController smartReplyController, NotificationEntry entry,
-            HeadsUpManager headsUpManager, boolean delayOnClickListener) {
+    public List<Button> inflateSmartActions(Context packageContext,
+            @NonNull SmartActions smartActions, SmartReplyController smartReplyController,
+            NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
         List<Button> buttons = new ArrayList<>();
         int numSmartActions = smartActions.actions.size();
         for (int n = 0; n < numSmartActions; n++) {
             Notification.Action action = smartActions.actions.get(n);
             if (action.actionIntent != null) {
                 buttons.add(inflateActionButton(
-                        this, getContext(), n, smartActions, smartReplyController, entry,
-                        headsUpManager, delayOnClickListener));
+                        this, getContext(), packageContext, n, smartActions, smartReplyController,
+                        entry, headsUpManager, delayOnClickListener));
             }
         }
         return buttons;
@@ -265,7 +265,7 @@
             NotificationEntry entry, boolean useDelayedOnClickListener) {
         Button b = (Button) LayoutInflater.from(context).inflate(
                 R.layout.smart_reply_button, smartReplyView, false);
-        CharSequence choice = smartReplies.choices[replyIndex];
+        CharSequence choice = smartReplies.choices.get(replyIndex);
         b.setText(choice);
 
         OnDismissAction action = () -> {
@@ -327,7 +327,7 @@
 
     @VisibleForTesting
     static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
-            int actionIndex, SmartActions smartActions,
+            Context packageContext, int actionIndex, SmartActions smartActions,
             SmartReplyController smartReplyController, NotificationEntry entry,
             HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
         Notification.Action action = smartActions.actions.get(actionIndex);
@@ -335,7 +335,9 @@
                 R.layout.smart_action_button, smartReplyView, false);
         button.setText(action.title);
 
-        Drawable iconDrawable = action.getIcon().loadDrawable(context);
+        // We received the Icon from the application - so use the Context of the application to
+        // reference icon resources.
+        Drawable iconDrawable = action.getIcon().loadDrawable(packageContext);
         // Add the action icon to the Smart Action button.
         int newIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.smart_action_button_icon_size);
@@ -944,10 +946,10 @@
         @NonNull
         public final PendingIntent pendingIntent;
         @NonNull
-        public final CharSequence[] choices;
+        public final List<CharSequence> choices;
         public final boolean fromAssistant;
 
-        public SmartReplies(CharSequence[] choices, RemoteInput remoteInput,
+        public SmartReplies(List<CharSequence> choices, RemoteInput remoteInput,
                 PendingIntent pendingIntent, boolean fromAssistant) {
             this.choices = choices;
             this.remoteInput = remoteInput;
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 2e1e520..95ae23c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -92,7 +92,7 @@
     private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
     private final GuestResumeSessionReceiver mGuestResumeSessionReceiver
             = new GuestResumeSessionReceiver();
-    private final KeyguardMonitor mKeyguardMonitor;
+    private final KeyguardStateController mKeyguardStateController;
     protected final Handler mHandler;
     private final ActivityStarter mActivityStarter;
 
@@ -109,13 +109,13 @@
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
 
     @Inject
-    public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor,
+    public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
             @Named(MAIN_HANDLER_NAME) Handler handler, ActivityStarter activityStarter) {
         mContext = context;
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(context);
         }
-        mKeyguardMonitor = keyguardMonitor;
+        mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mActivityStarter = activityStarter;
         mUserManager = UserManager.get(context);
@@ -148,7 +148,7 @@
         // Fetch initial values.
         mSettingsObserver.onChange(false);
 
-        keyguardMonitor.addCallback(mCallback);
+        keyguardStateController.addCallback(mCallback);
         listenForCallState();
 
         refreshUsers(UserHandle.USER_NULL);
@@ -596,18 +596,18 @@
     public static abstract class BaseUserAdapter extends BaseAdapter {
 
         final UserSwitcherController mController;
-        private final KeyguardMonitor mKeyguardMonitor;
+        private final KeyguardStateController mKeyguardStateController;
 
         protected BaseUserAdapter(UserSwitcherController controller) {
             mController = controller;
-            mKeyguardMonitor = controller.mKeyguardMonitor;
+            mKeyguardStateController = controller.mKeyguardStateController;
             controller.addAdapter(new WeakReference<>(this));
         }
 
         public int getUserCount() {
-            boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
-                    && mKeyguardMonitor.isSecure()
-                    && !mKeyguardMonitor.canSkipBouncer();
+            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
+                    && mKeyguardStateController.isMethodSecure()
+                    && !mKeyguardStateController.canDismissLockScreen();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
@@ -627,9 +627,9 @@
 
         @Override
         public int getCount() {
-            boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
-                    && mKeyguardMonitor.isSecure()
-                    && !mKeyguardMonitor.canSkipBouncer();
+            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
+                    && mKeyguardStateController.isMethodSecure()
+                    && !mKeyguardStateController.canDismissLockScreen();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
@@ -816,19 +816,21 @@
         }
     };
 
-    private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() {
-        @Override
-        public void onKeyguardShowingChanged() {
+    private final KeyguardStateController.Callback mCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardShowingChanged() {
 
-            // When Keyguard is going away, we don't need to update our items immediately which
-            // helps making the transition faster.
-            if (!mKeyguardMonitor.isShowing()) {
-                mHandler.post(UserSwitcherController.this::notifyAdapters);
-            } else {
-                notifyAdapters();
-            }
-        }
-    };
+                    // When Keyguard is going away, we don't need to update our items immediately
+                    // which
+                    // helps making the transition faster.
+                    if (!mKeyguardStateController.isShowing()) {
+                        mHandler.post(UserSwitcherController.this::notifyAdapters);
+                    } else {
+                        notifyAdapters();
+                    }
+                }
+            };
 
     private final class ExitGuestDialog extends SystemUIDialog implements
             DialogInterface.OnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 1d4f9b3..453c2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,12 +33,19 @@
 import com.android.systemui.R;
 import com.android.systemui.fragments.FragmentService;
 
+import javax.inject.Inject;
+
 public class TunerActivity extends Activity implements
         PreferenceFragment.OnPreferenceStartFragmentCallback,
         PreferenceFragment.OnPreferenceStartScreenCallback {
 
     private static final String TAG_TUNER = "tuner";
 
+    @Inject
+    TunerActivity() {
+        super();
+    }
+
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -50,8 +57,6 @@
             setActionBar(toolbar);
         }
 
-        Dependency.initDependencies(this);
-
         if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) {
             final String action = getIntent().getAction();
             boolean showDemoMode = action != null && action.equals(
@@ -67,7 +72,6 @@
     protected void onDestroy() {
         super.onDestroy();
         Dependency.destroy(FragmentService.class, s -> s.destroyAll());
-        Dependency.clearDependencies();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
deleted file mode 100644
index b9c5ee5..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
+++ /dev/null
@@ -1,228 +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.util;
-
-import android.content.Context;
-import android.hardware.HardwareBuffer;
-import android.hardware.Sensor;
-import android.hardware.SensorAdditionalInfo;
-import android.hardware.SensorDirectChannel;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.TriggerEventListener;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.MemoryFile;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Wrapper around sensor manager that hides potential sources of latency.
- *
- * Offloads fetching (non-dynamic) sensors and (un)registering listeners onto a background thread
- * without blocking. Note that this means registering listeners now always appears successful even
- * if it is not.
- */
-@Singleton
-public class AsyncSensorManager extends SensorManager
-        implements PluginListener<SensorManagerPlugin> {
-
-    private static final String TAG = "AsyncSensorManager";
-
-    private final SensorManager mInner;
-    private final List<Sensor> mSensorCache;
-    private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
-    @VisibleForTesting final Handler mHandler;
-    private final List<SensorManagerPlugin> mPlugins;
-
-    @Inject
-    public AsyncSensorManager(Context context, PluginManager pluginManager) {
-        this(context.getSystemService(SensorManager.class), pluginManager);
-    }
-
-    @VisibleForTesting
-    AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) {
-        mInner = sensorManager;
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
-        mPlugins = new ArrayList<>();
-        pluginManager.addPluginListener(this, SensorManagerPlugin.class, true /* allowMultiple */);
-    }
-
-    @Override
-    protected List<Sensor> getFullSensorList() {
-        return mSensorCache;
-    }
-
-    @Override
-    protected List<Sensor> getFullDynamicSensorList() {
-        return mInner.getSensorList(Sensor.TYPE_ALL);
-    }
-
-    @Override
-    protected boolean registerListenerImpl(SensorEventListener listener,
-            Sensor sensor, int delayUs, Handler handler, int maxReportLatencyUs,
-            int reservedFlags) {
-        mHandler.post(() -> {
-            if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
-                Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
-            }
-        });
-        return true;
-    }
-
-    @Override
-    protected boolean flushImpl(SensorEventListener listener) {
-        return mInner.flush(listener);
-    }
-
-    @Override
-    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
-            HardwareBuffer hardwareBuffer) {
-        throw new UnsupportedOperationException("not implemented");
-    }
-
-    @Override
-    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
-        throw new UnsupportedOperationException("not implemented");
-    }
-
-    @Override
-    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
-        throw new UnsupportedOperationException("not implemented");
-    }
-
-    @Override
-    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
-            Handler handler) {
-        mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler));
-    }
-
-    @Override
-    protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) {
-        mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback));
-    }
-
-    @Override
-    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
-        mHandler.post(() -> {
-            if (!mInner.requestTriggerSensor(listener, sensor)) {
-                Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
-            }
-        });
-        return true;
-    }
-
-    @Override
-    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
-            boolean disable) {
-        Preconditions.checkArgument(disable);
-
-        mHandler.post(() -> {
-            if (!mInner.cancelTriggerSensor(listener, sensor)) {
-                Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed.");
-            }
-        });
-        return true;
-    }
-
-    /**
-     * Requests for all sensors that match the given type from all plugins.
-     * @param sensor
-     * @param listener
-     * @return true if there were plugins to register the listener to
-     */
-    public boolean registerPluginListener(SensorManagerPlugin.Sensor sensor,
-            SensorManagerPlugin.SensorEventListener listener) {
-        if (mPlugins.isEmpty()) {
-            Log.w(TAG, "No plugins registered");
-            return false;
-        }
-        mHandler.post(() -> {
-            for (int i = 0; i < mPlugins.size(); i++) {
-                mPlugins.get(i).registerListener(sensor, listener);
-            }
-        });
-
-        return true;
-    }
-
-    /**
-     * Unregisters all sensors that match the give type for all plugins.
-     * @param sensor
-     * @param listener
-     */
-    public void unregisterPluginListener(SensorManagerPlugin.Sensor sensor,
-            SensorManagerPlugin.SensorEventListener listener) {
-        mHandler.post(() -> {
-            for (int i = 0; i < mPlugins.size(); i++) {
-                mPlugins.get(i).unregisterListener(sensor, listener);
-            }
-        });
-    }
-
-    @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
-        throw new UnsupportedOperationException("not implemented");
-    }
-
-    @Override
-    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
-            long timestamp) {
-        throw new UnsupportedOperationException("not implemented");
-    }
-
-    @Override
-    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
-        mHandler.post(() -> mInner.setOperationParameter(parameter));
-        return true;
-    }
-
-    @Override
-    protected void unregisterListenerImpl(SensorEventListener listener,
-            Sensor sensor) {
-        mHandler.post(() -> {
-            if (sensor == null) {
-                mInner.unregisterListener(listener);
-            } else {
-                mInner.unregisterListener(listener, sensor);
-            }
-        });
-    }
-
-    @Override
-    public void onPluginConnected(SensorManagerPlugin plugin, Context pluginContext) {
-        mPlugins.add(plugin);
-    }
-
-    @Override
-    public void onPluginDisconnected(SensorManagerPlugin plugin) {
-        mPlugins.remove(plugin);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
new file mode 100644
index 0000000..3a2172a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -0,0 +1,131 @@
+/*
+ * 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.util;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Wrapper around DeviceConfig useful for testing.
+ */
+public class DeviceConfigProxy {
+    @Inject
+    public DeviceConfigProxy() {
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#addOnPropertiesChangedListener}.
+     */
+    public void addOnPropertiesChangedListener(
+            @NonNull String namespace,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+        DeviceConfig.addOnPropertiesChangedListener(
+                namespace, executor, onPropertiesChangedListener);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
+     */
+    public void enforceReadPermission(Context context, String namespace) {
+        DeviceConfig.enforceReadPermission(context, namespace);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getBoolean}.
+     */
+    public boolean getBoolean(
+            @NonNull String namespace, @NonNull String name, boolean defaultValue) {
+        return DeviceConfig.getBoolean(namespace, name, defaultValue);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getFloat}.
+     */
+    public float getFloat(
+            @NonNull String namespace, @NonNull String name, float defaultValue) {
+        return DeviceConfig.getFloat(namespace, name, defaultValue);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getInt}.
+     */
+    public int getInt(@NonNull String namespace, @NonNull String name, int defaultValue) {
+        return DeviceConfig.getInt(namespace, name, defaultValue);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getLong}.
+     */
+    public long getLong(@NonNull String namespace, @NonNull String name, long defaultValue) {
+        return DeviceConfig.getLong(namespace, name, defaultValue);
+
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getProperty}.
+     */
+    public String getProperty(@NonNull String namespace, @NonNull String name) {
+        return DeviceConfig.getProperty(namespace, name);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#getString}.
+     */
+    public String getString(
+            @NonNull String namespace, @NonNull String name, @Nullable String defaultValue) {
+        return DeviceConfig.getString(namespace, name, defaultValue);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#removeOnPropertiesChangedListener}.
+     *
+     * Like {@link #addOnPropertiesChangedListener}, this operates on a callback type that
+     * wraps the original callback type provided by {@link DeviceConfig}.
+     */
+    public void removeOnPropertiesChangedListener(
+            @NonNull DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+        DeviceConfig.removeOnPropertiesChangedListener(onPropertiesChangedListener);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#resetToDefaults}.
+     */
+    public void resetToDefaults(@Settings.ResetMode int resetMode,
+            @Nullable String namespace) {
+        DeviceConfig.resetToDefaults(resetMode, namespace);
+    }
+
+    /**
+     * Wrapped version of {@link DeviceConfig#setProperty}.
+     */
+    public boolean setProperty(
+            @NonNull String namespace,
+            @NonNull String name,
+            @Nullable String value,
+            boolean makeDefault) {
+        return DeviceConfig.setProperty(namespace, name, value, makeDefault);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 9b264c4..e44e58a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -172,6 +173,11 @@
          * Creates the QuickQSPanel.
          */
         QuickQSPanel createQuickQSPanel();
+
+        /**
+         * Creates the QSCustomizer.
+         */
+        QSCustomizer createQSCustomizer();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
new file mode 100644
index 0000000..dcd0c58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -0,0 +1,236 @@
+/*
+ * 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.util.sensors;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEventListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Wrapper around sensor manager that hides potential sources of latency.
+ *
+ * Offloads fetching (non-dynamic) sensors and (un)registering listeners onto a background thread
+ * without blocking. Note that this means registering listeners now always appears successful even
+ * if it is not.
+ */
+@Singleton
+public class AsyncSensorManager extends SensorManager
+        implements PluginListener<SensorManagerPlugin> {
+
+    private static final String TAG = "AsyncSensorManager";
+
+    private final SensorManager mInner;
+    private final List<Sensor> mSensorCache;
+    private final Handler mHandler;
+    private final List<SensorManagerPlugin> mPlugins;
+
+    @Inject
+    public AsyncSensorManager(Context context, PluginManager pluginManager) {
+        this(context.getSystemService(SensorManager.class), pluginManager, null);
+    }
+
+    @VisibleForTesting
+    public AsyncSensorManager(
+            SensorManager sensorManager, PluginManager pluginManager, Handler handler) {
+        mInner = sensorManager;
+        if (handler == null) {
+            HandlerThread handlerThread = new HandlerThread("async_sensor");
+            handlerThread.start();
+            mHandler = new Handler(handlerThread.getLooper());
+        } else {
+            mHandler = handler;
+        }
+        mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
+        mPlugins = new ArrayList<>();
+        if (pluginManager != null) {
+            pluginManager.addPluginListener(this, SensorManagerPlugin.class,
+                    true /* allowMultiple */);
+        }
+    }
+
+    @Override
+    protected List<Sensor> getFullSensorList() {
+        return mSensorCache;
+    }
+
+    @Override
+    protected List<Sensor> getFullDynamicSensorList() {
+        return mInner.getSensorList(Sensor.TYPE_ALL);
+    }
+
+    @Override
+    protected boolean registerListenerImpl(SensorEventListener listener,
+            Sensor sensor, int delayUs, Handler handler, int maxReportLatencyUs,
+            int reservedFlags) {
+        mHandler.post(() -> {
+            if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
+                Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean flushImpl(SensorEventListener listener) {
+        return mInner.flush(listener);
+    }
+
+    @Override
+    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
+            HardwareBuffer hardwareBuffer) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
+            Handler handler) {
+        mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler));
+    }
+
+    @Override
+    protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) {
+        mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback));
+    }
+
+    @Override
+    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        mHandler.post(() -> {
+            if (!mInner.requestTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+            boolean disable) {
+        Preconditions.checkArgument(disable);
+
+        mHandler.post(() -> {
+            if (!mInner.cancelTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    /**
+     * Requests for all sensors that match the given type from all plugins.
+     * @param sensor
+     * @param listener
+     * @return true if there were plugins to register the listener to
+     */
+    public boolean registerPluginListener(SensorManagerPlugin.Sensor sensor,
+            SensorManagerPlugin.SensorEventListener listener) {
+        if (mPlugins.isEmpty()) {
+            Log.w(TAG, "No plugins registered");
+            return false;
+        }
+        mHandler.post(() -> {
+            for (int i = 0; i < mPlugins.size(); i++) {
+                mPlugins.get(i).registerListener(sensor, listener);
+            }
+        });
+
+        return true;
+    }
+
+    /**
+     * Unregisters all sensors that match the give type for all plugins.
+     * @param sensor
+     * @param listener
+     */
+    public void unregisterPluginListener(SensorManagerPlugin.Sensor sensor,
+            SensorManagerPlugin.SensorEventListener listener) {
+        mHandler.post(() -> {
+            for (int i = 0; i < mPlugins.size(); i++) {
+                mPlugins.get(i).unregisterListener(sensor, listener);
+            }
+        });
+    }
+
+    @Override
+    protected boolean initDataInjectionImpl(boolean enable) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
+            long timestamp) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+        mHandler.post(() -> mInner.setOperationParameter(parameter));
+        return true;
+    }
+
+    @Override
+    protected void unregisterListenerImpl(SensorEventListener listener,
+            Sensor sensor) {
+        mHandler.post(() -> {
+            if (sensor == null) {
+                mInner.unregisterListener(listener);
+            } else {
+                mInner.unregisterListener(listener, sensor);
+            }
+        });
+    }
+
+    @Override
+    public void onPluginConnected(SensorManagerPlugin plugin, Context pluginContext) {
+        mPlugins.add(plugin);
+    }
+
+    @Override
+    public void onPluginDisconnected(SensorManagerPlugin plugin) {
+        mPlugins.remove(plugin);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
new file mode 100644
index 0000000..cce5bca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -0,0 +1,305 @@
+/*
+ * 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.util.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Simple wrapper around SensorManager customized for the Proximity sensor.
+ */
+public class ProximitySensor {
+    private static final String TAG = "ProxSensor";
+    private static final boolean DEBUG = false;
+
+    private final Sensor mSensor;
+    private final AsyncSensorManager mSensorManager;
+    private final boolean mUsingBrightnessSensor;
+    private final float mMaxRange;
+    private List<ProximitySensorListener> mListeners = new ArrayList<>();
+    private String mTag = null;
+    @VisibleForTesting ProximityEvent mLastEvent;
+    private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
+    private boolean mPaused;
+    private boolean mRegistered;
+
+    private SensorEventListener mSensorEventListener = new SensorEventListener() {
+        @Override
+        public synchronized void onSensorChanged(SensorEvent event) {
+            onSensorEvent(event);
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+
+    @Inject
+    public ProximitySensor(Context context, AsyncSensorManager sensorManager) {
+        mSensorManager = sensorManager;
+        Sensor sensor = findBrightnessSensor(context);
+
+        if (sensor == null) {
+            mUsingBrightnessSensor = false;
+            sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        } else {
+            mUsingBrightnessSensor = true;
+        }
+        mSensor = sensor;
+        if (mSensor != null) {
+            mMaxRange = mSensor.getMaximumRange();
+        } else {
+            mMaxRange = 0;
+        }
+    }
+
+    public void setTag(String tag) {
+        mTag = tag;
+    }
+
+    public void setSensorDelay(int sensorDelay) {
+        mSensorDelay = sensorDelay;
+    }
+
+    /**
+     * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+     */
+    public void pause() {
+        mPaused = true;
+        unregisterInternal();
+    }
+
+    /**
+     * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+     */
+    public void resume() {
+        mPaused = false;
+        registerInternal();
+    }
+
+    private Sensor findBrightnessSensor(Context context) {
+        String sensorType = context.getString(R.string.doze_brightness_sensor_type);
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        Sensor sensor = null;
+        for (Sensor s : sensorList) {
+            if (sensorType.equals(s.getStringType())) {
+                sensor = s;
+                break;
+            }
+        }
+
+        return sensor;
+    }
+
+    /**
+     * Returns true if we are registered with the SensorManager.
+     */
+    public boolean isRegistered() {
+        return mRegistered;
+    }
+
+    /**
+     * Returns {@code false} if a Proximity sensor is not available.
+     */
+    public boolean getSensorAvailable() {
+        return mSensor != null;
+    }
+
+    /**
+     * Add a listener.
+     *
+     * Registers itself with the {@link SensorManager} if this is the first listener
+     * added. If a cool down is currently running, the sensor will be registered when it is over.
+     */
+    public boolean register(ProximitySensorListener listener) {
+        if (!getSensorAvailable()) {
+            return false;
+        }
+
+        mListeners.add(listener);
+        registerInternal();
+
+        return true;
+    }
+
+    protected void registerInternal() {
+        if (mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
+        logDebug("Registering sensor listener");
+        mRegistered = true;
+        mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
+    }
+
+    /**
+     * Remove a listener.
+     *
+     * If all listeners are removed from an instance of this class,
+     * it will unregister itself with the SensorManager.
+     */
+    public void unregister(ProximitySensorListener listener) {
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            unregisterInternal();
+        }
+    }
+
+    protected void unregisterInternal() {
+        if (!mRegistered) {
+            return;
+        }
+        logDebug("unregistering sensor listener");
+        mSensorManager.unregisterListener(mSensorEventListener);
+        mRegistered = false;
+    }
+
+    public Boolean isNear() {
+        return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null;
+    }
+
+    /** Update all listeners with the last value this class received from the sensor. */
+    public void alertListeners() {
+        mListeners.forEach(proximitySensorListener ->
+                proximitySensorListener.onSensorEvent(mLastEvent));
+    }
+
+    private void onSensorEvent(SensorEvent event) {
+        boolean near = event.values[0] < mMaxRange;
+        if (mUsingBrightnessSensor) {
+            near = event.values[0] == 0;
+        }
+        mLastEvent = new ProximityEvent(near, event.timestamp);
+        alertListeners();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}",
+                isRegistered(), mPaused, isNear(), mSensor);
+    }
+
+    /**
+     * Convenience class allowing for briefly checking the proximity sensor.
+     */
+    public static class ProximityCheck implements Runnable {
+
+        private final ProximitySensor mSensor;
+        private final Handler mHandler;
+        private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+
+        @Inject
+        public ProximityCheck(ProximitySensor sensor, Handler handler) {
+            mSensor = sensor;
+            mSensor.setTag("prox_check");
+            mHandler = handler;
+            mSensor.pause();
+            ProximitySensorListener listener = proximityEvent -> {
+                mCallbacks.forEach(
+                        booleanConsumer ->
+                                booleanConsumer.accept(
+                                        proximityEvent == null ? null : proximityEvent.getNear()));
+                mCallbacks.clear();
+                mSensor.pause();
+            };
+            mSensor.register(listener);
+        }
+
+        /** Set a descriptive tag for the sensors registration. */
+        public void setTag(String tag) {
+            mSensor.setTag(tag);
+        }
+
+        @Override
+        public void run() {
+            mSensor.pause();
+            mSensor.alertListeners();
+        }
+
+        /**
+         * Query the proximity sensor, timing out if no result.
+         */
+        public void check(long timeoutMs, Consumer<Boolean> callback) {
+            if (!mSensor.getSensorAvailable()) {
+                callback.accept(null);
+            }
+            mCallbacks.add(callback);
+            if (!mSensor.isRegistered()) {
+                mSensor.resume();
+                mHandler.postDelayed(this, timeoutMs);
+            }
+        }
+    }
+
+    /** Implement to be notified of ProximityEvents. */
+    public interface ProximitySensorListener {
+        /** Called when the ProximitySensor changes. */
+        void onSensorEvent(ProximityEvent proximityEvent);
+    }
+
+    /**
+     * Returned when the near/far state of a {@link ProximitySensor} changes.
+     */
+    public static class ProximityEvent {
+        private final boolean mNear;
+        private final long mTimestampNs;
+
+        public ProximityEvent(boolean near, long timestampNs) {
+            mNear = near;
+            mTimestampNs = timestampNs;
+        }
+
+        public boolean getNear() {
+            return mNear;
+        }
+
+        public long getTimestampNs() {
+            return mTimestampNs;
+        }
+
+        public long getTimestampMs() {
+            return mTimestampNs / 1000000;
+        }
+
+        @Override
+        public String toString() {
+            return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs);
+        }
+
+    }
+
+    private void logDebug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5412cde..81e2c22 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -39,7 +39,7 @@
     telephony-common \
     android.test.base \
 
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui:com.android.keyguard
+LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
 
 # sign this with platform cert, so this test is allowed to inject key events into
 # UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 1ae1b97..2e94c7c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -22,11 +22,14 @@
 import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
 
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -34,18 +37,22 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.provider.Settings;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
@@ -75,6 +82,9 @@
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
             TEST_CARRIER_ID, 0);
+    private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0,
+            TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
+            null, null, null, null, false, null, "");
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
@@ -111,6 +121,7 @@
         mDependency.injectMockDependency(WakefulnessLifecycle.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 new Handler(mTestableLooper.getLooper()));
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
 
         mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
@@ -279,6 +290,65 @@
     }
 
     @Test
+    public void testCarrierText_noTextOnReadySimWhenNull() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
+                TextUtils.isEmpty(captor.getValue().carrierText));
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+    }
+
+    @Test
+    public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+        mockWifi();
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+        ServiceState ss = mock(ServiceState.class);
+        when(ss.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+        // There's no airplane mode if at least one SIM is State.READY and there's wifi
+        assertFalse("Device should not be in airplane mode", captor.getValue().airplaneMode);
+        assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+    }
+
+    private void mockWifi() {
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.getBSSID()).thenReturn("");
+        when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
+    }
+
+    @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
index dcafa72..00f88bf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
@@ -27,6 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index f01c0b4..de7664c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -39,6 +39,7 @@
 import android.widget.TextClock;
 
 import com.android.keyguard.clock.ClockManager;
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
index a69fd56..25f279b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
@@ -39,6 +39,7 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mKeyguardHostView = new KeyguardHostView(getContext());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
index 5f03bdb..b4363cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
@@ -21,6 +21,7 @@
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.google.common.truth.Truth.assertThat
@@ -41,6 +42,7 @@
     @Before
     fun setup() {
         val inflater = LayoutInflater.from(context)
+        mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
         mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null)
                 as KeyguardPatternView
         mSecurityMessage = KeyguardMessageArea(mContext, null,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
index eadb1b6..6666a92 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
@@ -28,6 +28,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -52,6 +53,7 @@
     @Before
     public void setup() {
         LayoutInflater inflater = LayoutInflater.from(getContext());
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mKeyguardPinView =
                 (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null);
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 8138420..082782d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -36,6 +36,7 @@
 public class KeyguardPresentationTest extends SysuiTestCase {
     @Test
     public void testInflation_doesntCrash() {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 8db195a..d47fcee 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index b3accbc..116f8fc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -27,6 +27,7 @@
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
 
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 31ea39c..e4b83cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.Assert;
@@ -50,6 +51,7 @@
     @Before
     public void setUp() {
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
         LayoutInflater layoutInflater = inflationController
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a3cb6c0..ad7bba1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -51,7 +51,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
@@ -72,12 +71,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-// We must run on the main looper because KeyguardUpdateMonitor#mHandler is initialized with
-// new Handler(Looper.getMainLooper()).
-//
-// Using the main looper should be avoided whenever possible, please don't copy this over to
-// new tests.
-@RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
@@ -477,6 +471,16 @@
         assertThat(listToVerify.get(0)).isEqualTo(TEST_SUBSCRIPTION_2);
     }
 
+    @Test
+    public void testIsUserUnlocked() {
+        // mUserManager will report the user as unlocked on @Before
+        assertThat(mKeyguardUpdateMonitor.isUserUnlocked(KeyguardUpdateMonitor.getCurrentUser()))
+                .isTrue();
+        // Invalid user should not be unlocked.
+        int randomUser = 99;
+        assertThat(mKeyguardUpdateMonitor.isUserUnlocked(randomUser)).isFalse();
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
@@ -491,7 +495,7 @@
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
 
         protected TestableKeyguardUpdateMonitor(Context context) {
-            super(context);
+            super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper());
             context.unregisterReceiver(mBroadcastReceiver);
             context.unregisterReceiver(mBroadcastAllReceiver);
             mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index b1ca169..939df10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -74,6 +74,9 @@
     @Test
     public void testInitDependency() {
         Dependency.clearDependencies();
-        Dependency.initDependencies(mContext);
+        Dependency dependency = new Dependency();
+        SystemUIFactory
+                .getInstance().getRootComponent().createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index e07d17f..a07f25a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -29,6 +29,7 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Assert;
@@ -48,6 +49,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         Context context = getContext();
         mRow = new NotificationTestHelper(context).createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 8a6ee12..5c4ef18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -37,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -392,19 +393,27 @@
     }
 
     private void entryRemoved(StatusBarNotification notification) {
-        mEntryListener.onEntryRemoved(new NotificationEntry(notification),
-                null, false);
+        mEntryListener.onEntryRemoved(
+                new NotificationEntryBuilder()
+                        .setSbn(notification)
+                        .build(),
+                null,
+                false);
     }
 
     private void entryAdded(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntry(notification);
-        entry.importance = importance;
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSbn(notification)
+                .setImportance(importance)
+                .build();
         mEntryListener.onPendingEntryAdded(entry);
     }
 
     private void entryUpdated(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntry(notification);
-        entry.importance = importance;
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSbn(notification)
+                .setImportance(importance)
+                .build();
         mEntryListener.onPostEntryUpdated(entry);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
new file mode 100644
index 0000000..212c93d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Notification;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
+    private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
+    private NotificationEntry mEntry;
+    private Notification mNotif;
+
+    @Before
+    public void setup() {
+        mNotif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setContentTitle("Title")
+                .setContentText("Text")
+                .build();
+
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotif)
+                .build();
+    }
+
+    /**
+     * ForegroundServiceLifetimeExtenderTest
+     */
+    @Test
+    public void testShouldExtendLifetime_should_foreground() {
+        // Extend the lifetime of a FGS notification iff it has not been visible
+        // for the minimum time
+        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        modifySbn(mEntry)
+                .setPostTime(System.currentTimeMillis())
+                .build();
+        assertTrue(mExtender.shouldExtendLifetime(mEntry));
+    }
+
+    @Test
+    public void testShouldExtendLifetime_shouldNot_foreground() {
+        mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        modifySbn(mEntry)
+                .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
+                .build();
+        assertFalse(mExtender.shouldExtendLifetime(mEntry));
+    }
+
+    @Test
+    public void testShouldExtendLifetime_shouldNot_notForeground() {
+        mNotif.flags = 0;
+        modifySbn(mEntry)
+                .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
+                .build();
+        assertFalse(mExtender.shouldExtendLifetime(mEntry));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 3b5e12c..64ab060 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -45,7 +45,6 @@
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicyConstants;
 
 import androidx.test.filters.SmallTest;
 
@@ -53,7 +52,6 @@
 import com.android.systemui.ScreenDecorations.TunablePaddingTagListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.tuner.TunablePadding;
@@ -80,7 +78,6 @@
     private TunerService mTunerService;
     private StatusBarWindowView mView;
     private TunablePaddingService mTunablePaddingService;
-    private NavigationModeController mNavigationModeController;
 
     @Before
     public void setup() {
@@ -90,8 +87,6 @@
         mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class);
         mTunerService = mDependency.injectMockDependency(TunerService.class);
         mFragmentService = mDependency.injectMockDependency(FragmentService.class);
-        mNavigationModeController = mDependency.injectMockDependency(
-                NavigationModeController.class);
 
         mStatusBar = mock(StatusBar.class);
         mWindowManager = mock(WindowManager.class);
@@ -213,54 +208,6 @@
     }
 
     @Test
-    public void testAssistHandles() {
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius, 0);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius_top, 0);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
-        mContext.getOrCreateTestableResources()
-                .addOverride(dimen.rounded_corner_content_padding, 0);
-        when(mNavigationModeController.addListener(any())).thenReturn(
-                WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL);
-
-        mScreenDecorations.start();
-
-        // Add 2 windows for rounded corners (top and bottom).
-        verify(mWindowManager, times(2)).addView(any(), any());
-    }
-
-    @Test
-    public void testDelayedAssistHandles() {
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius, 0);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius_top, 0);
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.dimen.rounded_corner_radius_bottom, 0);
-        mContext.getOrCreateTestableResources()
-                .addOverride(dimen.rounded_corner_content_padding, 0);
-        when(mNavigationModeController.addListener(any())).thenReturn(
-                WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON);
-
-        mScreenDecorations.start();
-
-        // No handles and no corners
-        verify(mWindowManager, never()).addView(any(), any());
-
-        mScreenDecorations.handleNavigationModeChange(
-                WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL);
-
-        // Add 2 windows for rounded corners (top and bottom).
-        verify(mWindowManager, times(2)).addView(any(), any());
-    }
-
-    @Test
     public void hasRoundedCornerOverlayFlagSet() {
         assertThat(mScreenDecorations.getWindowLayoutParams().privateFlags
                         & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
index 256cfb2..a245d41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -42,7 +42,7 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
-    protected final TestableDependency mDependency = new TestableDependency(mContext);
+    protected TestableDependency mDependency;
     protected SysuiTestableContext mSysuiContext;
     private Instrumentation mRealInstrumentation;
 
@@ -53,6 +53,7 @@
     @Before
     public void SysuiSetup() {
         SystemUIFactory.createFromConfig(mContext);
+        mDependency = new TestableDependency(mContext);
         // TODO: Figure out another way to give reference to a SysuiTestableContext.
         mSysuiContext = (SysuiTestableContext) mContext;
 
@@ -69,6 +70,7 @@
     public void SysuiTeardown() {
         InstrumentationRegistry.registerInstance(mRealInstrumentation,
                 InstrumentationRegistry.getArguments());
+        SystemUIFactory.cleanup();
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 8d93f96..819a7f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -30,6 +30,8 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.util.Assert;
 
 import org.junit.After;
@@ -55,12 +57,13 @@
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
-    public TestableDependency mDependency = new TestableDependency(mContext);
+    public TestableDependency mDependency;
     private Instrumentation mRealInstrumentation;
 
     @Before
     public void SysuiSetup() throws Exception {
         SystemUIFactory.createFromConfig(mContext);
+        mDependency = new TestableDependency(mContext);
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
@@ -73,7 +76,10 @@
                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
         });
         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
-        KeyguardUpdateMonitor.disableHandlerCheckForTesting(inst);
+        // A lot of tests get the FalsingManager, often via several layers of indirection.
+        // None of them actually need it.
+        mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
     }
 
     @After
@@ -82,6 +88,7 @@
                 InstrumentationRegistry.getArguments());
         // Reset the assert's main looper.
         Assert.sMainLooper = Looper.getMainLooper();
+        SystemUIFactory.cleanup();
     }
 
     protected LeakCheck getLeakCheck() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 18bf75e..0c53b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -25,10 +25,6 @@
     private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>();
 
     public TestableDependency(Context context) {
-        if (context instanceof SysuiTestableContext) {
-            mComponents = ((SysuiTestableContext) context).getComponents();
-        }
-        mContext = context;
         SystemUIFactory.createFromConfig(context);
         SystemUIFactory.getInstance().getRootComponent()
                 .createDependency()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index a583b1c..fbb8e0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -37,10 +37,11 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.ScreenDecorations;
+import com.android.systemui.DumpController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,6 +51,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.EnumMap;
+import java.util.Map;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -59,28 +63,44 @@
 
     private AssistHandleBehaviorController mAssistHandleBehaviorController;
 
-    @Mock private ScreenDecorations mMockScreenDecorations;
     @Mock private AssistUtils mMockAssistUtils;
     @Mock private Handler mMockHandler;
     @Mock private PhenotypeHelper mMockPhenotypeHelper;
-    @Mock private AssistHandleBehaviorController.BehaviorController mMockBehaviorController;
+    @Mock private AssistHandleOffBehavior mMockOffBehavior;
+    @Mock private AssistHandleLikeHomeBehavior mMockLikeHomeBehavior;
+    @Mock private AssistHandleReminderExpBehavior mMockReminderExpBehavior;
+    @Mock private AssistHandleBehaviorController.BehaviorController mMockTestBehavior;
+    @Mock private NavigationModeController mMockNavigationModeController;
+    @Mock private DumpController mMockDumpController;
+    @Mock private AssistHandleViewController mMockAssistHandleViewController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
+        mDependency.injectMockDependency(DumpController.class);
         doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class));
         doAnswer(answerVoid(Runnable::run)).when(mMockHandler)
                 .postDelayed(any(Runnable.class), anyLong());
 
+        Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController> behaviorMap =
+                new EnumMap<>(AssistHandleBehavior.class);
+        behaviorMap.put(AssistHandleBehavior.OFF, mMockOffBehavior);
+        behaviorMap.put(AssistHandleBehavior.LIKE_HOME, mMockLikeHomeBehavior);
+        behaviorMap.put(AssistHandleBehavior.REMINDER_EXP, mMockReminderExpBehavior);
+        behaviorMap.put(AssistHandleBehavior.TEST, mMockTestBehavior);
+
         mAssistHandleBehaviorController =
                 new AssistHandleBehaviorController(
                         mContext,
                         mMockAssistUtils,
-                        mMockHandler, () -> mMockScreenDecorations,
+                        mMockHandler,
+                        () -> mMockAssistHandleViewController,
                         mMockPhenotypeHelper,
-                        mMockBehaviorController);
+                        behaviorMap,
+                        mMockNavigationModeController,
+                        mMockDumpController);
     }
 
     @After
@@ -93,14 +113,14 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.showAndStay();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.hide();
 
         // Assert
-        verify(mMockScreenDecorations).setAssistHintVisible(false);
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verify(mMockAssistHandleViewController).setAssistHintVisible(false);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -108,13 +128,13 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.hide();
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -122,14 +142,14 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndStay();
 
         // Assert
-        verify(mMockScreenDecorations).setAssistHintVisible(true);
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verify(mMockAssistHandleViewController).setAssistHintVisible(true);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -137,13 +157,13 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.showAndStay();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndStay();
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -151,13 +171,13 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndStay();
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -165,15 +185,15 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGo();
 
         // Assert
-        InOrder inOrder = inOrder(mMockScreenDecorations);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(true);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(false);
+        InOrder inOrder = inOrder(mMockAssistHandleViewController);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(true);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(false);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -182,14 +202,14 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.showAndStay();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGo();
 
         // Assert
-        verify(mMockScreenDecorations).setAssistHintVisible(false);
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verify(mMockAssistHandleViewController).setAssistHintVisible(false);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -200,13 +220,13 @@
                 eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS),
                 anyLong())).thenReturn(10000L);
         mAssistHandleBehaviorController.showAndGo();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGo();
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -214,13 +234,13 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGo();
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -228,15 +248,15 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGoDelayed(1000, false);
 
         // Assert
-        InOrder inOrder = inOrder(mMockScreenDecorations);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(true);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(false);
+        InOrder inOrder = inOrder(mMockAssistHandleViewController);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(true);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(false);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -245,14 +265,14 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.showAndStay();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGoDelayed(1000, false);
 
         // Assert
-        verify(mMockScreenDecorations).setAssistHintVisible(false);
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verify(mMockAssistHandleViewController).setAssistHintVisible(false);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -260,16 +280,16 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.showAndStay();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGoDelayed(1000, true);
 
         // Assert
-        InOrder inOrder = inOrder(mMockScreenDecorations);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(false);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(true);
-        inOrder.verify(mMockScreenDecorations).setAssistHintVisible(false);
+        InOrder inOrder = inOrder(mMockAssistHandleViewController);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(false);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(true);
+        inOrder.verify(mMockAssistHandleViewController).setAssistHintVisible(false);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -281,13 +301,13 @@
                 eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS),
                 anyLong())).thenReturn(10000L);
         mAssistHandleBehaviorController.showAndGo();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGoDelayed(1000, false);
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -295,13 +315,13 @@
         // Arrange
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(null);
         mAssistHandleBehaviorController.hide();
-        reset(mMockScreenDecorations);
+        reset(mMockAssistHandleViewController);
 
         // Act
         mAssistHandleBehaviorController.showAndGoDelayed(1000, false);
 
         // Assert
-        verifyNoMoreInteractions(mMockScreenDecorations);
+        verifyNoMoreInteractions(mMockAssistHandleViewController);
     }
 
     @Test
@@ -314,8 +334,8 @@
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
 
         // Assert
-        verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController);
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verify(mMockTestBehavior).onModeActivated(mContext, mAssistHandleBehaviorController);
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 
     @Test
@@ -324,14 +344,14 @@
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
         mAssistHandleBehaviorController.setInGesturalModeForTest(true);
-        reset(mMockBehaviorController);
+        reset(mMockTestBehavior);
 
         // Act
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.OFF);
 
         // Assert
-        verify(mMockBehaviorController).onModeDeactivated();
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verify(mMockTestBehavior).onModeDeactivated();
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 
     @Test
@@ -344,6 +364,6 @@
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
 
         // Assert
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java
new file mode 100644
index 0000000..fe13127
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleLikeHomeBehaviorTest.java
@@ -0,0 +1,364 @@
+/*
+ * 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.assist;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
+
+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)
+@TestableLooper.RunWithLooper
+public class AssistHandleLikeHomeBehaviorTest extends SysuiTestCase {
+
+    private AssistHandleLikeHomeBehavior mAssistHandleLikeHomeBehavior;
+
+    @Mock private StatusBarStateController mMockStatusBarStateController;
+    @Mock private WakefulnessLifecycle mMockWakefulnessLifecycle;
+    @Mock private SysUiState mMockSysUiState;
+    @Mock private AssistHandleCallbacks mMockAssistHandleCallbacks;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mAssistHandleLikeHomeBehavior = new AssistHandleLikeHomeBehavior(
+                () -> mMockStatusBarStateController,
+                () -> mMockWakefulnessLifecycle,
+                () -> mMockSysUiState);
+    }
+
+    @Test
+    public void onModeActivated_beginsObserving() {
+        // Arrange
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+
+        // Assert
+        verify(mMockStatusBarStateController).isDozing();
+        verify(mMockStatusBarStateController).addCallback(
+                any(StatusBarStateController.StateListener.class));
+        verify(mMockWakefulnessLifecycle).getWakefulness();
+        verify(mMockWakefulnessLifecycle).addObserver(any(WakefulnessLifecycle.Observer.class));
+        verify(mMockSysUiState).addCallback(any(SysUiState.SysUiStateCallback.class));
+        verifyNoMoreInteractions(mMockWakefulnessLifecycle, mMockSysUiState);
+    }
+
+    @Test
+    public void onModeActivated_showsHandlesWhenFullyAwake() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(false);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).showAndStay();
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onModeActivated_hidesHandlesWhenNotAwake() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(true);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onModeActivated_hidesHandlesWhenDozing() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(true);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onModeDeactivated_stopsObserving() {
+        // Arrange
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListener =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        ArgumentCaptor<WakefulnessLifecycle.Observer> observer =
+                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
+        ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback =
+                ArgumentCaptor.forClass(SysUiState.SysUiStateCallback.class);
+        verify(mMockStatusBarStateController).addCallback(stateListener.capture());
+        verify(mMockWakefulnessLifecycle).addObserver(observer.capture());
+        verify(mMockSysUiState).addCallback(sysUiStateCallback.capture());
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onModeDeactivated();
+
+        // Assert
+        verify(mMockStatusBarStateController).removeCallback(eq(stateListener.getValue()));
+        verify(mMockWakefulnessLifecycle).removeObserver(eq(observer.getValue()));
+        verify(mMockSysUiState).removeCallback(eq(sysUiStateCallback.getValue()));
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onAssistantGesturePerformed_doesNothing() {
+        // Arrange
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onAssistantGesturePerformed();
+
+        // Assert
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onAssistHandlesRequested_doesNothing() {
+        // Arrange
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleLikeHomeBehavior.onAssistHandlesRequested();
+
+        // Assert
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onBothAwakeAndUnDoze_handlesShow() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(true);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP);
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListener =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        ArgumentCaptor<WakefulnessLifecycle.Observer> observer =
+                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        verify(mMockStatusBarStateController).addCallback(stateListener.capture());
+        verify(mMockWakefulnessLifecycle).addObserver(observer.capture());
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        observer.getValue().onFinishedWakingUp();
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Arrange
+        observer.getValue().onFinishedGoingToSleep();
+        reset(mMockAssistHandleCallbacks);
+
+        // Act
+        stateListener.getValue().onDozingChanged(false);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        observer.getValue().onFinishedWakingUp();
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).showAndStay();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onSleepOrDoze_handlesHide() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(false);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        ArgumentCaptor<StatusBarStateController.StateListener> stateListener =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        ArgumentCaptor<WakefulnessLifecycle.Observer> observer =
+                ArgumentCaptor.forClass(WakefulnessLifecycle.Observer.class);
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        verify(mMockStatusBarStateController).addCallback(stateListener.capture());
+        verify(mMockWakefulnessLifecycle).addObserver(observer.capture());
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        observer.getValue().onStartedGoingToSleep();
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Arrange
+        observer.getValue().onFinishedWakingUp();
+        reset(mMockAssistHandleCallbacks);
+
+        // Act
+        stateListener.getValue().onDozingChanged(true);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onHomeHandleHide_handlesHide() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(false);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback =
+                ArgumentCaptor.forClass(SysUiState.SysUiStateCallback.class);
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        verify(mMockSysUiState).addCallback(sysUiStateCallback.capture());
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        sysUiStateCallback.getValue().onSystemUiStateChanged(
+                QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onHomeHandleUnhide_handlesShow() {
+        // Arrange
+        when(mMockStatusBarStateController.isDozing()).thenReturn(false);
+        when(mMockWakefulnessLifecycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        ArgumentCaptor<SysUiState.SysUiStateCallback> sysUiStateCallback =
+                ArgumentCaptor.forClass(SysUiState.SysUiStateCallback.class);
+        mAssistHandleLikeHomeBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        verify(mMockSysUiState).addCallback(sysUiStateCallback.capture());
+        sysUiStateCallback.getValue().onSystemUiStateChanged(
+                QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN);
+        reset(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+
+        // Act
+        sysUiStateCallback.getValue().onSystemUiStateChanged(
+                ~QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).showAndStay();
+        verifyNoMoreInteractions(
+                mMockStatusBarStateController,
+                mMockWakefulnessLifecycle,
+                mMockSysUiState,
+                mMockAssistHandleCallbacks);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleOffBehaviorTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleOffBehaviorTest.java
new file mode 100644
index 0000000..15d4d5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleOffBehaviorTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.assist;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AssistHandleOffBehaviorTest extends SysuiTestCase {
+
+    private AssistHandleOffBehavior mAssistHandleOffBehavior;
+
+    @Mock private AssistHandleCallbacks mMockAssistHandleCallbacks;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mAssistHandleOffBehavior = new AssistHandleOffBehavior();
+    }
+
+    @Test
+    public void onModeActivated_hidesHandles() {
+        // Arrange
+
+        // Act
+        mAssistHandleOffBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+
+        // Assert
+        verify(mMockAssistHandleCallbacks).hide();
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onModeDeactivated_doesNothing() {
+        // Arrange
+        mAssistHandleOffBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        reset(mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleOffBehavior.onModeDeactivated();
+
+        // Assert
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onAssistantGesturePerformed_doesNothing() {
+        // Arrange
+        mAssistHandleOffBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        reset(mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleOffBehavior.onAssistantGesturePerformed();
+
+        // Assert
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+
+    @Test
+    public void onAssistHandlesRequested_doesNothing() {
+        // Arrange
+        mAssistHandleOffBehavior.onModeActivated(mContext, mMockAssistHandleCallbacks);
+        reset(mMockAssistHandleCallbacks);
+
+        // Act
+        mAssistHandleOffBehavior.onAssistHandlesRequested();
+
+        // Assert
+        verifyNoMoreInteractions(mMockAssistHandleCallbacks);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleViewControllerTest.java
new file mode 100644
index 0000000..6e21ae2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleViewControllerTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.assist;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.CornerHandleView;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class AssistHandleViewControllerTest extends SysuiTestCase {
+
+    private AssistHandleViewController mAssistHandleViewController;
+
+    @Mock private Handler mMockHandler;
+    @Mock private Looper mMockLooper;
+    @Mock private View mMockBarView;
+    @Mock private CornerHandleView mMockAssistHint;
+    @Mock private ViewPropertyAnimator mMockAnimator;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockBarView.findViewById(anyInt())).thenReturn(mMockAssistHint);
+        when(mMockAssistHint.animate()).thenReturn(mMockAnimator);
+        when(mMockAnimator.setInterpolator(any())).thenReturn(mMockAnimator);
+        when(mMockAnimator.setDuration(anyLong())).thenReturn(mMockAnimator);
+        doNothing().when(mMockAnimator).cancel();
+        when(mMockHandler.getLooper()).thenReturn(mMockLooper);
+        when(mMockLooper.isCurrentThread()).thenReturn(true);
+
+        mAssistHandleViewController = new AssistHandleViewController(mMockHandler, mMockBarView);
+    }
+
+    @Test
+    public void testSetVisibleWithoutBlocked() {
+        // Act
+        mAssistHandleViewController.setAssistHintVisible(true);
+
+        // Assert
+        assertTrue(mAssistHandleViewController.mAssistHintVisible);
+    }
+
+    @Test
+    public void testSetInvisibleWithoutBlocked() {
+        // Arrange
+        mAssistHandleViewController.setAssistHintVisible(true);
+
+        // Act
+        mAssistHandleViewController.setAssistHintVisible(false);
+
+        // Assert
+        assertFalse(mAssistHandleViewController.mAssistHintVisible);
+    }
+
+    @Test
+    public void testSetVisibleWithBlocked() {
+        // Act
+        mAssistHandleViewController.setAssistHintBlocked(true);
+        mAssistHandleViewController.setAssistHintVisible(true);
+
+        // Assert
+        assertFalse(mAssistHandleViewController.mAssistHintVisible);
+        assertTrue(mAssistHandleViewController.mAssistHintBlocked);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
new file mode 100644
index 0000000..b907cdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import com.android.systemui.R;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricFaceViewTest extends SysuiTestCase {
+
+    @Mock
+    AuthBiometricView.Callback mCallback;
+
+    private TestableFaceView mFaceView;
+
+    @Mock private Button mNegativeButton;
+    @Mock private Button mPositiveButton;
+    @Mock private Button mTryAgainButton;
+    @Mock private TextView mErrorView;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mFaceView = new TestableFaceView(mContext);
+        mFaceView.mIconController = mock(TestableFaceView.TestableIconController.class);
+        mFaceView.setCallback(mCallback);
+        mFaceView.mNegativeButton = mNegativeButton;
+        mFaceView.mPositiveButton = mPositiveButton;
+        mFaceView.mTryAgainButton = mTryAgainButton;
+        mFaceView.mIndicatorView = mErrorView;
+    }
+
+    @Test
+    public void testStateUpdated_whenDialogAnimatedIn() {
+        mFaceView.onDialogAnimatedIn();
+        verify(mFaceView.mIconController)
+                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+    }
+
+    @Test
+    public void testIconUpdatesState_whenDialogStateUpdated() {
+        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
+        verify(mFaceView.mIconController)
+                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+
+        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
+        verify(mFaceView.mIconController).updateState(
+                eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
+                eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
+    }
+
+    public class TestableFaceView extends AuthBiometricFaceView {
+
+        public class TestableIconController extends IconController {
+            TestableIconController(Context context, ImageView iconView) {
+                super(context, iconView, mock(TextView.class));
+            }
+
+            public void startPulsing() {
+                // Stub for testing
+            }
+        }
+
+        @Override
+        protected int getDelayAfterAuthenticatedDurationMs() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+
+        public TestableFaceView(Context context) {
+            super(context);
+        }
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
new file mode 100644
index 0000000..7a09137
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -0,0 +1,374 @@
+/*
+ * 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.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricViewTest extends SysuiTestCase {
+
+    @Mock private AuthBiometricView.Callback mCallback;
+    @Mock private AuthPanelController mPanelController;
+
+    @Mock private Button mNegativeButton;
+    @Mock private Button mPositiveButton;
+    @Mock private Button mTryAgainButton;
+    @Mock private TextView mTitleView;
+    @Mock private TextView mSubtitleView;
+    @Mock private TextView mDescriptionView;
+    @Mock private TextView mIndicatorView;
+    @Mock private ImageView mIconView;
+
+    private TestableBiometricView mBiometricView;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        // The onAuthenticated runnable is posted when authentication succeeds.
+        mBiometricView.onAuthenticationSucceeded();
+        waitForIdleSync();
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+    }
+
+    @Test
+    public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        mBiometricView.setRequireConfirmation(true);
+        mBiometricView.onAuthenticationSucceeded();
+        waitForIdleSync();
+        assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
+        verify(mCallback, never()).onAction(anyInt());
+        verify(mBiometricView.mNegativeButton).setText(eq(R.string.cancel));
+        verify(mBiometricView.mPositiveButton).setEnabled(eq(true));
+        verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
+        verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
+    }
+
+    @Test
+    public void testPositiveButton_sendsActionAuthenticated() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+           @Override
+            public Button getPositiveButton() {
+               return button;
+           }
+        });
+
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+    }
+
+    @Test
+    public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getNegativeButton() {
+                return button;
+            }
+        });
+
+        mBiometricView.onDialogAnimatedIn();
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+    }
+
+    @Test
+    public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getNegativeButton() {
+                return button;
+            }
+        });
+
+        mBiometricView.setRequireConfirmation(true);
+        mBiometricView.onAuthenticationSucceeded();
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
+    }
+
+    @Test
+    public void testTryAgainButton_sendsActionTryAgain() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return button;
+            }
+        });
+
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
+    }
+
+    @Test
+    public void testError_sendsActionError() {
+        initDialog(mContext, mCallback, new MockInjector());
+        final String testError = "testError";
+        mBiometricView.onError(testError);
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_ERROR);
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+    }
+
+    @Test
+    public void testBackgroundClicked_sendsActionUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        view.performClick();
+        verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        mBiometricView.onAuthenticationSucceeded();
+        view.performClick();
+        verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+        mBiometricView.setPanelController(mPanelController);
+        mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        view.performClick();
+        verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testRestoresState() {
+        final boolean requireConfirmation = true; // set/init from AuthController
+
+        Button tryAgainButton = new Button(mContext);
+        TextView indicatorView = new TextView(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return tryAgainButton;
+            }
+            @Override
+            public TextView getIndicatorView() {
+                return indicatorView;
+            }
+        });
+
+        final String failureMessage = "testFailureMessage";
+        mBiometricView.setRequireConfirmation(requireConfirmation);
+        mBiometricView.onAuthenticationFailed(failureMessage);
+        waitForIdleSync();
+
+        Bundle state = new Bundle();
+        mBiometricView.onSaveState(state);
+
+        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+        assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+        assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+
+        assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+        assertTrue(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING));
+
+        assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
+        assertEquals(failureMessage, state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING));
+
+        // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
+
+        // Create new dialog and restore the previous state into it
+        Button tryAgainButton2 = new Button(mContext);
+        TextView indicatorView2 = new TextView(mContext);
+        initDialog(mContext, mCallback, state, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return tryAgainButton2;
+            }
+            @Override
+            public TextView getIndicatorView() {
+                return indicatorView2;
+            }
+        });
+        mBiometricView.setRequireConfirmation(requireConfirmation);
+        waitForIdleSync();
+
+        // Test restored state
+        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+        assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+
+        // TODO: Test restored text. Currently cannot test this, since it gets restored only after
+        // dialog size is known.
+    }
+
+    private Bundle buildBiometricPromptBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
+        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
+        return bundle;
+    }
+
+    private void initDialog(Context context, AuthBiometricView.Callback callback,
+            Bundle savedState, MockInjector injector) {
+        mBiometricView = new TestableBiometricView(context, null, injector);
+        mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle());
+        mBiometricView.setCallback(callback);
+        mBiometricView.restoreState(savedState);
+        mBiometricView.onFinishInflateInternal();
+        mBiometricView.onAttachedToWindowInternal();
+    }
+
+    private void initDialog(Context context, AuthBiometricView.Callback callback,
+            MockInjector injector) {
+        initDialog(context, callback, null /* savedState */, injector);
+    }
+
+    private class MockInjector extends AuthBiometricView.Injector {
+        @Override
+        public Button getNegativeButton() {
+            return mNegativeButton;
+        }
+
+        @Override
+        public Button getPositiveButton() {
+            return mPositiveButton;
+        }
+
+        @Override
+        public Button getTryAgainButton() {
+            return mTryAgainButton;
+        }
+
+        @Override
+        public TextView getTitleView() {
+            return mTitleView;
+        }
+
+        @Override
+        public TextView getSubtitleView() {
+            return mSubtitleView;
+        }
+
+        @Override
+        public TextView getDescriptionView() {
+            return mDescriptionView;
+        }
+
+        @Override
+        public TextView getIndicatorView() {
+            return mIndicatorView;
+        }
+
+        @Override
+        public ImageView getIconView() {
+            return mIconView;
+        }
+
+        @Override
+        public int getDelayAfterError() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+    }
+
+    private class TestableBiometricView extends AuthBiometricView {
+        TestableBiometricView(Context context, AttributeSet attrs,
+                Injector injector) {
+            super(context, attrs, injector);
+        }
+
+        @Override
+        protected int getDelayAfterAuthenticatedDurationMs() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+
+        @Override
+        protected int getStateForAfterError() {
+            return 0;
+        }
+
+        @Override
+        protected void handleResetAfterError() {
+
+        }
+
+        @Override
+        protected void handleResetAfterHelp() {
+
+        }
+
+        @Override
+        protected boolean supportsSmallDialog() {
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
new file mode 100644
index 0000000..d4fc3f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthContainerViewTest extends SysuiTestCase {
+
+    private TestableAuthContainer mAuthContainer;
+
+    private @Mock AuthDialogCallback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        AuthContainerView.Config config = new AuthContainerView.Config();
+        config.mContext = mContext;
+        config.mCallback = mCallback;
+        mAuthContainer = new TestableAuthContainer(config);
+    }
+
+    @Test
+    public void testActionAuthenticated_sendsDismissedAuthenticated() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_AUTHENTICATED));
+    }
+
+    @Test
+    public void testActionUserCanceled_sendsDismissedUserCanceled() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_USER_CANCELED);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED));
+    }
+
+    @Test
+    public void testActionButtonNegative_sendsDismissedButtonNegative() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE));
+    }
+
+    @Test
+    public void testActionTryAgain_sendsTryAgain() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+        verify(mCallback).onTryAgainPressed();
+    }
+
+    @Test
+    public void testActionError_sendsDismissedError() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_ERROR);
+        verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR);
+    }
+
+    private class TestableAuthContainer extends AuthContainerView {
+        TestableAuthContainer(AuthContainerView.Config config) {
+            super(config);
+        }
+
+        @Override
+        public void animateAway(int reason) {
+            mConfig.mCallback.onDismissed(reason);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
new file mode 100644
index 0000000..eb7be4f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.biometrics;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.IActivityTaskManager;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthControllerTest extends SysuiTestCase {
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private IBiometricServiceReceiverInternal mReceiver;
+    @Mock
+    private AuthDialog mDialog1;
+    @Mock
+    private AuthDialog mDialog2;
+
+    private TestableBiometricDialogImpl mBiometricDialogImpl;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        TestableContext context = spy(mContext);
+
+        mContext.putComponent(StatusBar.class, mock(StatusBar.class));
+        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+
+        when(context.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
+            .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+            .thenReturn(true);
+
+        when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
+        when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
+
+        mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector());
+        mBiometricDialogImpl.mContext = context;
+        mBiometricDialogImpl.mComponents = mContext.getComponents();
+
+        mBiometricDialogImpl.start();
+    }
+
+    // Callback tests
+
+    @Test
+    public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+    }
+
+    @Test
+    public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+    }
+
+    @Test
+    public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
+    }
+
+    @Test
+    public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_AUTHENTICATED);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
+    }
+
+    @Test
+    public void testSendsReasonError_whenDismissedByError() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
+    }
+
+    @Test
+    public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
+        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+    }
+
+    // Statusbar tests
+
+    @Test
+    public void testShowInvoked_whenSystemRequested()
+            throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        verify(mDialog1).show(any(), any());
+    }
+
+    @Test
+    public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */);
+        verify(mDialog1).onAuthenticationSucceeded();
+    }
+
+    @Test
+    public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        final String failureReason = "failure reason";
+        mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason);
+
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        verify(mDialog1).onAuthenticationFailed(captor.capture());
+
+        assertEquals(captor.getValue(), failureReason);
+    }
+
+    @Test
+    public void testOnHelpInvoked_whenSystemRequested() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        final String helpMessage = "help";
+        mBiometricDialogImpl.onBiometricHelp(helpMessage);
+
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        verify(mDialog1).onHelp(captor.capture());
+
+        assertEquals(captor.getValue(), helpMessage);
+    }
+
+    @Test
+    public void testOnErrorInvoked_whenSystemRequested() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        final String errMessage = "error message";
+        mBiometricDialogImpl.onBiometricError(errMessage);
+
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        verify(mDialog1).onError(captor.capture());
+
+        assertEquals(captor.getValue(), errMessage);
+    }
+
+    @Test
+    public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.hideBiometricDialog();
+        verify(mDialog1).dismissFromSystemServer();
+    }
+
+    @Test
+    public void testClientNotified_whenDismissedBySystemServer() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        mBiometricDialogImpl.hideBiometricDialog();
+        verify(mDialog1).dismissFromSystemServer();
+
+        assertNotNull(mBiometricDialogImpl.mCurrentDialog);
+        assertNotNull(mBiometricDialogImpl.mReceiver);
+    }
+
+    // Corner case tests
+
+    @Test
+    public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        verify(mDialog1).show(any(), any());
+
+        showDialog(BiometricPrompt.TYPE_FACE);
+
+        // First dialog should be dismissed without animation
+        verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
+
+        // Second dialog should be shown without animation
+        verify(mDialog2).show(any(), any());
+    }
+
+    @Test
+    public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+        verify(mDialog1).show(any(), any());
+
+        mBiometricDialogImpl.onConfigurationChanged(new Configuration());
+
+        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mDialog1).onSaveState(captor.capture());
+
+        // Old dialog doesn't animate
+        verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
+
+        // Saved state is restored into new dialog
+        ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
+        verify(mDialog2).show(any(), captor2.capture());
+
+        // TODO: This should check all values we want to save/restore
+        assertEquals(captor.getValue(), captor2.getValue());
+    }
+
+    @Test
+    public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
+        showDialog(BiometricPrompt.TYPE_FACE);
+
+        List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
+        ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+        taskInfo.topActivity = mock(ComponentName.class);
+        when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
+        tasks.add(taskInfo);
+        when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
+
+        mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged();
+        waitForIdleSync();
+
+        assertNull(mBiometricDialogImpl.mCurrentDialog);
+        assertNull(mBiometricDialogImpl.mReceiver);
+        verify(mDialog1).dismissWithoutCallback(true /* animate */);
+        verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
+    }
+
+    // Helpers
+
+    private void showDialog(int type) {
+        mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(),
+                mReceiver /* receiver */,
+                type,
+                true /* requireConfirmation */,
+                0 /* userId */,
+                "testPackage");
+    }
+
+    private Bundle createTestDialogBundle() {
+        Bundle bundle = new Bundle();
+
+        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
+        bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, "Subtitle");
+        bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, "Description");
+        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative Button");
+
+        // RequireConfirmation is a hint to BiometricService. This can be forced to be required
+        // by user settings, and should be tested in BiometricService.
+        bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
+
+        return bundle;
+    }
+
+    private final class TestableBiometricDialogImpl extends AuthController {
+        private int mBuildCount = 0;
+
+        public TestableBiometricDialogImpl(Injector injector) {
+            super(injector);
+        }
+
+        @Override
+        protected AuthDialog buildDialog(Bundle biometricPromptBundle,
+                boolean requireConfirmation, int userId, int type, String opPackageName,
+                boolean skipIntro) {
+            AuthDialog dialog;
+            if (mBuildCount == 0) {
+                dialog = mDialog1;
+            } else if (mBuildCount == 1) {
+                dialog = mDialog2;
+            } else {
+                dialog = null;
+            }
+            mBuildCount++;
+            return dialog;
+        }
+    }
+
+    private final class MockInjector extends AuthController.Injector {
+        @Override
+        IActivityTaskManager getActivityTaskManager() {
+            return mock(IActivityTaskManager.class);
+        }
+    }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
deleted file mode 100644
index 8f2f8b1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
+++ /dev/null
@@ -1,329 +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.biometrics;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.assertNotNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.IActivityTaskManager;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.os.Bundle;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper.RunWithLooper;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-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;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class BiometricDialogImplTest extends SysuiTestCase {
-
-    @Mock
-    private PackageManager mPackageManager;
-    @Mock
-    private IBiometricServiceReceiverInternal mReceiver;
-    @Mock
-    private BiometricDialog mDialog1;
-    @Mock
-    private BiometricDialog mDialog2;
-
-    private TestableBiometricDialogImpl mBiometricDialogImpl;
-
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        TestableContext context = spy(mContext);
-
-        mContext.putComponent(StatusBar.class, mock(StatusBar.class));
-        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
-
-        when(context.getPackageManager()).thenReturn(mPackageManager);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
-            .thenReturn(true);
-        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
-            .thenReturn(true);
-
-        when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
-        when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
-
-        mBiometricDialogImpl = new TestableBiometricDialogImpl(new MockInjector());
-        mBiometricDialogImpl.mContext = context;
-        mBiometricDialogImpl.mComponents = mContext.getComponents();
-
-        mBiometricDialogImpl.start();
-    }
-
-    // Callback tests
-
-    @Test
-    public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_USER_CANCELED);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
-    }
-
-    @Test
-    public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
-    }
-
-    @Test
-    public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
-    }
-
-    @Test
-    public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_AUTHENTICATED);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
-    }
-
-    @Test
-    public void testSendsReasonError_whenDismissedByError() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_ERROR);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
-    }
-
-    @Test
-    public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
-    }
-
-    // Statusbar tests
-
-    @Test
-    public void testShowInvoked_whenSystemRequested()
-            throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
-    }
-
-    @Test
-    public void testOnAuthenticationSucceededInvoked_whenSystemRequested() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onBiometricAuthenticated(true, null /* failureReason */);
-        verify(mDialog1).onAuthenticationSucceeded();
-    }
-
-    @Test
-    public void testOnAuthenticationFailedInvoked_whenSystemRequested() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        final String failureReason = "failure reason";
-        mBiometricDialogImpl.onBiometricAuthenticated(false, failureReason);
-
-        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onAuthenticationFailed(captor.capture());
-
-        assertEquals(captor.getValue(), failureReason);
-    }
-
-    @Test
-    public void testOnHelpInvoked_whenSystemRequested() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        final String helpMessage = "help";
-        mBiometricDialogImpl.onBiometricHelp(helpMessage);
-
-        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onHelp(captor.capture());
-
-        assertEquals(captor.getValue(), helpMessage);
-    }
-
-    @Test
-    public void testOnErrorInvoked_whenSystemRequested() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        final String errMessage = "error message";
-        mBiometricDialogImpl.onBiometricError(errMessage);
-
-        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-        verify(mDialog1).onError(captor.capture());
-
-        assertEquals(captor.getValue(), errMessage);
-    }
-
-    @Test
-    public void testDismissWithoutCallbackInvoked_whenSystemRequested() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.hideBiometricDialog();
-        verify(mDialog1).dismissFromSystemServer();
-    }
-
-    @Test
-    public void testClientNotified_whenDismissedBySystemServer() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.hideBiometricDialog();
-        verify(mDialog1).dismissFromSystemServer();
-
-        assertNotNull(mBiometricDialogImpl.mCurrentDialog);
-        assertNotNull(mBiometricDialogImpl.mReceiver);
-    }
-
-    // Corner case tests
-
-    @Test
-    public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
-
-        showDialog(BiometricPrompt.TYPE_FACE);
-
-        // First dialog should be dismissed without animation
-        verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
-
-        // Second dialog should be shown without animation
-        verify(mDialog2).show(any(), eq(true)) /* skipIntro */;
-    }
-
-    @Test
-    public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
-
-        mBiometricDialogImpl.onConfigurationChanged(new Configuration());
-
-        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog1).onSaveState(captor.capture());
-
-        // Old dialog doesn't animate
-        verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
-
-        // Saved state is restored into new dialog
-        ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog2).restoreState(captor2.capture());
-
-        // Dialog for new configuration skips intro
-        verify(mDialog2).show(any(), eq(true) /* skipIntro */);
-
-        // TODO: This should check all values we want to save/restore
-        assertEquals(captor.getValue(), captor2.getValue());
-    }
-
-    @Test
-    public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
-        showDialog(BiometricPrompt.TYPE_FACE);
-
-        List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
-        ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
-        taskInfo.topActivity = mock(ComponentName.class);
-        when(taskInfo.topActivity.getPackageName()).thenReturn("other_package");
-        tasks.add(taskInfo);
-        when(mBiometricDialogImpl.mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
-
-        mBiometricDialogImpl.mTaskStackListener.onTaskStackChanged();
-        waitForIdleSync();
-
-        assertNull(mBiometricDialogImpl.mCurrentDialog);
-        assertNull(mBiometricDialogImpl.mReceiver);
-        verify(mDialog1).dismissWithoutCallback(true /* animate */);
-        verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
-    }
-
-    // Helpers
-
-    private void showDialog(int type) {
-        mBiometricDialogImpl.showBiometricDialog(createTestDialogBundle(),
-                mReceiver /* receiver */,
-                type,
-                true /* requireConfirmation */,
-                0 /* userId */,
-                "testPackage");
-    }
-
-    private Bundle createTestDialogBundle() {
-        Bundle bundle = new Bundle();
-
-        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
-        bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, "Subtitle");
-        bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, "Description");
-        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative Button");
-
-        // RequireConfirmation is a hint to BiometricService. This can be forced to be required
-        // by user settings, and should be tested in BiometricService.
-        bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
-
-        return bundle;
-    }
-
-    private final class TestableBiometricDialogImpl extends BiometricDialogImpl {
-        private int mBuildCount = 0;
-
-        public TestableBiometricDialogImpl(Injector injector) {
-            super(injector);
-        }
-
-        @Override
-        protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
-                boolean requireConfirmation, int userId, int type, String opPackageName) {
-            BiometricDialog dialog;
-            if (mBuildCount == 0) {
-                dialog = mDialog1;
-            } else if (mBuildCount == 1) {
-                dialog = mDialog2;
-            } else {
-                dialog = null;
-            }
-            mBuildCount++;
-            return dialog;
-        }
-    }
-
-    private final class MockInjector extends BiometricDialogImpl.Injector {
-        @Override
-        IActivityTaskManager getActivityTaskManager() {
-            return mock(IActivityTaskManager.class);
-        }
-    }
-}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
deleted file mode 100644
index bbdd837..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
+++ /dev/null
@@ -1,204 +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.biometrics.ui;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.spy;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.DialogViewCallback;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class BiometricDialogViewTest extends SysuiTestCase {
-
-    FaceDialogView mFaceDialogView;
-
-    private static final String TITLE = "Title";
-    private static final String SUBTITLE = "Subtitle";
-    private static final String DESCRIPTION = "Description";
-    private static final String NEGATIVE_BUTTON = "Negative Button";
-
-    private static final String TEST_HELP = "Help";
-
-    TestableContext mTestableContext;
-    @Mock
-    private DialogViewCallback mCallback;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private DevicePolicyManager mDpm;
-
-    private static class Injector extends BiometricDialogView.Injector {
-        @Override
-        public WakefulnessLifecycle getWakefulnessLifecycle() {
-            final WakefulnessLifecycle lifecycle = new WakefulnessLifecycle();
-            lifecycle.dispatchFinishedWakingUp();
-            return lifecycle;
-        }
-    }
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mTestableContext = spy(mContext);
-        mTestableContext.addMockSystemService(UserManager.class, mUserManager);
-        mTestableContext.addMockSystemService(DevicePolicyManager.class, mDpm);
-    }
-
-    @Test
-    public void testContentStates_confirmationRequired_authenticated() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                true /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-
-        // When starting authentication
-        assertEquals(View.VISIBLE, mFaceDialogView.mTitleText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
-        assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mNegativeButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-
-        // Contents are as expected
-        assertTrue(TITLE.contentEquals(mFaceDialogView.mTitleText.getText()));
-        assertTrue(SUBTITLE.contentEquals(mFaceDialogView.mSubtitleText.getText()));
-        assertTrue(DESCRIPTION.contentEquals(mFaceDialogView.mDescriptionText.getText()));
-        assertTrue(mFaceDialogView.mPositiveButton.getText().toString()
-                .contentEquals(mContext.getString(R.string.biometric_dialog_confirm)));
-        assertTrue(NEGATIVE_BUTTON.contentEquals(mFaceDialogView.mNegativeButton.getText()));
-        assertTrue(mFaceDialogView.mTryAgainButton.getText().toString()
-                .contentEquals(mContext.getString(R.string.biometric_dialog_try_again)));
-
-        // When help message is received
-        mFaceDialogView.onHelp(TEST_HELP);
-        assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
-        assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
-
-        // When authenticated, confirm button comes out
-        mFaceDialogView.onAuthenticationSucceeded();
-        assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(true, mFaceDialogView.mPositiveButton.isEnabled());
-    }
-
-    @Test
-    public void testContentStates_confirmationNotRequired_authenticated() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-        mFaceDialogView.updateSize(FaceDialogView.SIZE_SMALL);
-
-        assertEquals(View.INVISIBLE, mFaceDialogView.mTitleText.getVisibility());
-        assertNotSame(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
-        assertNotSame(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
-        assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-    }
-
-    @Test
-    public void testContentStates_confirmationNotRequired_help() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-
-        mFaceDialogView.onHelp(TEST_HELP);
-        assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
-        assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
-    }
-
-    @Test
-    public void testBack_sendsUserCanceled() {
-        // TODO: Need robolectric framework to wait for handler to complete
-    }
-
-    @Test
-    public void testScreenOff_sendsUserCanceled() {
-        // TODO: Need robolectric framework to wait for handler to complete
-    }
-
-    @Test
-    public void testRestoreState_contentStatesCorrect() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-        mFaceDialogView.onAuthenticationFailed(TEST_HELP);
-
-        final Bundle bundle = new Bundle();
-        mFaceDialogView.onSaveState(bundle);
-
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.restoreState(bundle);
-        mFaceDialogView.onAttachedToWindow();
-
-        assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility());
-    }
-
-    private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback,
-            boolean requireConfirmation) {
-        return (FaceDialogView) new BiometricDialogView.Builder(context)
-                .setCallback(callback)
-                .setBiometricPromptBundle(createTestDialogBundle())
-                .setRequireConfirmation(requireConfirmation)
-                .setUserId(0)
-                .setOpPackageName("test_package")
-                .build(BiometricDialogView.Builder.TYPE_FACE, new Injector());
-    }
-
-    private Bundle createTestDialogBundle() {
-        Bundle bundle = new Bundle();
-
-        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, TITLE);
-        bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, SUBTITLE);
-        bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, DESCRIPTION);
-        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, NEGATIVE_BUTTON);
-
-        // RequireConfirmation is a hint to BiometricService. This can be forced to be required
-        // by user settings, and should be tested in BiometricService.
-        bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
-
-        return bundle;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2221915..448c80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.bubbles;
 
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
@@ -28,6 +27,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,11 +43,10 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.drawable.Icon;
+import android.hardware.face.FaceManager;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -57,18 +57,22 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -82,20 +86,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubbleControllerTest extends SysuiTestCase {
 
-    // Some APIs rely on the app being foreground, check is via pkg name
-    private static final String FOREGROUND_TEST_PKG_NAME = "com.android.systemui.tests";
-
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
+    private NotificationGroupManager mNotificationGroupManager;
+    @Mock
     private WindowManager mWindowManager;
     @Mock
     private IActivityManager mActivityManager;
@@ -108,6 +108,10 @@
     @Mock
     private ZenModeConfig mZenModeConfig;
     @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
@@ -126,8 +130,6 @@
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
-    private ExpandableNotificationRow mAutoExpandRow;
-    private ExpandableNotificationRow mSuppressNotifRow;
     private ExpandableNotificationRow mNonBubbleNotifRow;
 
     @Mock
@@ -146,6 +148,7 @@
         MockitoAnnotations.initMocks(this);
         mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
+        mContext.addMockSystemService(FaceManager.class, mFaceManager);
 
         // Bubbles get added to status bar window view
         mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
@@ -159,35 +162,32 @@
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
 
-        // Some bubbles want to auto expand
-        Notification.BubbleMetadata autoExpandMetadata =
-                getBuilder().setAutoExpandBubble(true).build();
-        mAutoExpandRow = mNotificationTestHelper.createBubble(autoExpandMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
-        // Some bubbles want to suppress notifs
-        Notification.BubbleMetadata suppressNotifMetadata =
-                getBuilder().setSuppressNotification(true).build();
-        mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
-        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
+        when(mNotificationData.get(mRow.getEntry().key)).thenReturn(mRow.getEntry());
+        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(
+                mRow.getEntry().getChannel());
 
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
         TestableNotificationInterruptionStateProvider interruptionStateProvider =
-                new TestableNotificationInterruptionStateProvider(mContext);
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mock(NotificationFilter.class),
+                        mock(StatusBarStateController.class));
         interruptionStateProvider.setUpWithPresenter(
                 mock(NotificationPresenter.class),
                 mock(HeadsUpManager.class),
                 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
-        mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
-                mBubbleData, mConfigurationController, interruptionStateProvider,
-                mZenModeController);
+        mBubbleController = new TestableBubbleController(mContext,
+                mStatusBarWindowController,
+                mBubbleData,
+                mConfigurationController,
+                interruptionStateProvider,
+                mZenModeController,
+                mLockscreenUserManager,
+                mNotificationGroupManager);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -219,13 +219,14 @@
     @Test
     public void testRemoveBubble() {
         mBubbleController.updateBubble(mRow.getEntry());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
         verify(mNotificationEntryManager).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
 
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
-        assertTrue(mRow.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         verify(mNotificationEntryManager, times(2)).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
     }
@@ -236,10 +237,10 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Make it look like dismissed notif
-        mRow.getEntry().setShowInShadeWhenBubble(false);
+        mBubbleData.getBubbleWithKey(mRow.getEntry().key).setShowInShadeWhenBubble(false);
 
         // Now remove the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -255,15 +256,17 @@
     public void testDismissStack() {
         mBubbleController.updateBubble(mRow.getEntry());
         verify(mNotificationEntryManager, times(1)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         mBubbleController.updateBubble(mRow2.getEntry());
         verify(mNotificationEntryManager, times(2)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications();
-        assertTrue(mRow.getEntry().isBubbleDismissed());
-        assertTrue(mRow2.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
+        assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
     }
 
     @Test
@@ -274,9 +277,9 @@
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         assertFalse(mStatusBarWindowController.getBubbleExpanded());
 
         // Expand the stack
@@ -286,8 +289,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
         assertTrue(mStatusBarWindowController.getBubbleExpanded());
 
-        // Make sure it's no longer in the shade
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Make sure the notif is suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Collapse
         mBubbleController.collapseStack();
@@ -304,10 +307,11 @@
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
-        assertTrue(mRow2.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow2.getEntry().key));
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -317,13 +321,13 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Switch which bubble is expanded
         mBubbleController.selectBubble(mRow.getEntry().key);
-        stackView.setExpandedBubble(mRow.getEntry());
+        stackView.setExpandedBubble(mRow.getEntry().key);
         assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // collapse for previous bubble
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
@@ -336,14 +340,37 @@
     }
 
     @Test
-    public void testExpansionRemovesShowInShade() {
+    public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Expand
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
+
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+    }
+
+    @Test
+    public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -351,8 +378,19 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
 
-        // No longer show shade in notif after expansion
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Send update
+        mEntryListener.onPreEntryUpdated(mRow.getEntry());
+
+        // Nothing should have changed
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -373,7 +411,7 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Dismiss currently expanded
         mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
@@ -397,14 +435,16 @@
     @Test
     public void testAutoExpand_FailsNotForeground() {
         assertFalse(mBubbleController.isStackExpanded());
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion shouldn't change
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertFalse(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
@@ -413,91 +453,51 @@
 
     @Test
     public void testAutoExpand_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion should change
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertTrue(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
     public void testSuppressNotif_FailsNotForeground() {
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, false /* enableFlag */);
+
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should show in shade because we weren't forground
-        assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
-
+        // Should not be suppressed because we weren't forground
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
     }
 
     @Test
     public void testSuppressNotif_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should NOT show in shade because we were foreground
-        assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
+        // Notif should be suppressed because we were foreground
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
@@ -516,7 +516,8 @@
     @Test
     public void testMarkNewNotificationAsShowInShade() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -584,7 +585,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL_ALL);
@@ -592,7 +593,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -605,7 +606,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL);
@@ -613,7 +614,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -626,7 +627,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Dismiss the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -646,22 +647,21 @@
                 StatusBarWindowController statusBarWindowController, BubbleData data,
                 ConfigurationController configurationController,
                 NotificationInterruptionStateProvider interruptionStateProvider,
-                ZenModeController zenModeController) {
+                ZenModeController zenModeController,
+                NotificationLockscreenUserManager lockscreenUserManager,
+                NotificationGroupManager groupManager) {
             super(context, statusBarWindowController, data, Runnable::run,
-                    configurationController, interruptionStateProvider, zenModeController);
-        }
-
-        @Override
-        public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
-            return entry.notification.getNotification().getBubbleMetadata() != null;
+                    configurationController, interruptionStateProvider, zenModeController,
+                    lockscreenUserManager, groupManager);
         }
     }
 
-    public static class TestableNotificationInterruptionStateProvider extends
+    static class TestableNotificationInterruptionStateProvider extends
             NotificationInterruptionStateProvider {
 
-        public TestableNotificationInterruptionStateProvider(Context context) {
-            super(context);
+        TestableNotificationInterruptionStateProvider(Context context,
+                NotificationFilter filter, StatusBarStateController controller) {
+            super(context, filter, controller);
             mUseHeadsUp = true;
         }
     }
@@ -676,4 +676,21 @@
                 .setIntent(bubbleIntent)
                 .setIcon(Icon.createWithResource(mContext, R.drawable.android));
     }
+
+    /**
+     * Sets the bubble metadata flags for this entry. These flags are normally set by
+     * NotificationManagerService when the notification is sent, however, these tests do not
+     * go through that path so we set them explicitly when testing.
+     */
+    private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
+        Notification.BubbleMetadata bubbleMetadata =
+                entry.notification.getNotification().getBubbleMetadata();
+        int flags = bubbleMetadata.getFlags();
+        if (enableFlag) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+        bubbleMetadata.setFlags(flags);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 815a70a..392a7cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -37,6 +37,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleData.TimeSource;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -878,7 +879,7 @@
         when(sbn.getNotification()).thenReturn(notification);
 
         // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
-        return new NotificationEntry(sbn);
+        return new NotificationEntryBuilder().setSbn(sbn).build();
     }
 
     private void setCurrentTime(long time) {
@@ -887,7 +888,7 @@
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
         setPostTime(entry, postTime);
-        mBubbleData.notificationEntryUpdated(entry);
+        mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
     }
 
     private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
index 173237f..a8961a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -18,7 +18,6 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.verify;
 
@@ -46,6 +45,7 @@
 public class BubbleFlyoutViewTest extends SysuiTestCase {
     private BubbleFlyoutView mFlyout;
     private TextView mFlyoutText;
+    private float[] mDotCenter = new float[2];
 
     @Before
     public void setUp() throws Exception {
@@ -53,20 +53,25 @@
         mFlyout = new BubbleFlyoutView(getContext());
 
         mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+        mDotCenter[0] = 30;
+        mDotCenter[1] = 30;
     }
 
     @Test
     public void testShowFlyout_isVisible() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+        mFlyout.setVisibility(View.VISIBLE);
+
         assertEquals("Hello", mFlyoutText.getText());
         assertEquals(View.VISIBLE, mFlyout.getVisibility());
-        assertEquals(1f, mFlyoutText.getAlpha(), .01f);
     }
 
     @Test
     public void testFlyoutHide_runsCallback() {
         Runnable after = Mockito.mock(Runnable.class);
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, after);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter);
         mFlyout.hideFlyout();
 
         verify(after).run();
@@ -74,19 +79,16 @@
 
     @Test
     public void testSetCollapsePercent() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
-
-        float initialTranslationZ = mFlyout.getTranslationZ();
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+        mFlyout.setVisibility(View.VISIBLE);
 
         mFlyout.setCollapsePercent(1f);
         assertEquals(0f, mFlyoutText.getAlpha(), 0.01f);
         assertNotSame(0f, mFlyoutText.getTranslationX()); // Should have moved to collapse.
-        assertTrue(mFlyout.getTranslationZ() < initialTranslationZ); // Should be descending.
 
         mFlyout.setCollapsePercent(0f);
         assertEquals(1f, mFlyoutText.getAlpha(), 0.01f);
         assertEquals(0f, mFlyoutText.getTranslationX());
-        assertEquals(initialTranslationZ, mFlyout.getTranslationZ());
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
new file mode 100644
index 0000000..5757861
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.bubbles;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doReturn;
+
+import android.app.Notification;
+import android.os.Bundle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BubbleTest extends SysuiTestCase {
+    @Mock
+    private Notification mNotif;
+
+    private NotificationEntry mEntry;
+    private Bubble mBubble;
+    private Bundle mExtras;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mExtras = new Bundle();
+        mNotif.extras = mExtras;
+
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotif)
+                .build();
+        mBubble = new Bubble(mContext, mEntry);
+    }
+
+    @Test
+    public void testGetUpdateMessage_default() {
+        final String msg = "Hello there!";
+        doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_bigText() {
+        final String msg = "A big hello there!";
+        doReturn(Notification.BigTextStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequence(Notification.EXTRA_TEXT, "A small hello there.");
+        mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
+
+        // Should be big text, not the small text.
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_media() {
+        doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
+
+        // Media notifs don't get update messages.
+        assertNull(mBubble.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_inboxStyle() {
+        doReturn(Notification.InboxStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequenceArray(
+                Notification.EXTRA_TEXT_LINES,
+                new CharSequence[]{
+                        "How do you feel about tests?",
+                        "They're okay, I guess.",
+                        "I hate when they're flaky.",
+                        "Really? I prefer them that way."});
+
+        // Should be the last one only.
+        assertEquals("Really? I prefer them that way.", mBubble.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_messagingStyle() {
+        doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putParcelableArray(
+                Notification.EXTRA_MESSAGES,
+                new Bundle[]{
+                        new Notification.MessagingStyle.Message(
+                                "Hello", 0, "Josh").toBundle(),
+                        new Notification.MessagingStyle.Message(
+                                "Oh, hello!", 0, "Mady").toBundle()});
+
+        // Should be the last one only.
+        assertEquals("Mady: Oh, hello!", mBubble.getUpdateMessage(mContext));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
index 43d2ad1..8bc2e2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index b324235..ae4581a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -45,14 +46,18 @@
 
     private int mDisplayWidth = 500;
     private int mDisplayHeight = 1000;
+    private int mExpandedViewPadding = 10;
+    private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
+    private float mLauncherGridDiff = 30f;
 
     @Spy
     private ExpandedAnimationController mExpandedController =
             new ExpandedAnimationController(
                     new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
-                    0 /* expandedViewPadding */);
+                    mExpandedViewPadding, mOrientation);
+
     private int mStackOffset;
-    private float mBubblePadding;
+    private float mBubblePaddingTop;
     private float mBubbleSize;
 
     private PointF mExpansionPoint;
@@ -65,12 +70,13 @@
 
         Resources res = mLayout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mExpansionPoint = new PointF(100, 100);
     }
 
     @Test
+    @Ignore
     public void testExpansionAndCollapse() throws InterruptedException {
         Runnable afterExpand = Mockito.mock(Runnable.class);
         mExpandedController.expandFromStack(afterExpand);
@@ -88,6 +94,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildAdded() throws InterruptedException {
         expand();
 
@@ -100,6 +107,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildRemoved() throws InterruptedException {
         expand();
 
@@ -111,6 +119,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
         expand();
 
@@ -128,6 +137,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDismissed() throws InterruptedException {
         expand();
 
@@ -138,7 +148,6 @@
         assertEquals(500f, draggedBubble.getTranslationX(), 1f);
         assertEquals(500f, draggedBubble.getTranslationY(), 1f);
 
-        // Snap it back and make sure it made it back correctly.
         mLayout.removeView(draggedBubble);
         waitForLayoutMessageQueue();
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -174,7 +183,7 @@
 
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
 
-        assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+        assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
     }
 
     @Test
@@ -256,8 +265,8 @@
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleLeft = index * (mBubbleSize + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleLeft;
     }
 
     private float getRowLeft() {
@@ -265,16 +274,29 @@
             return 0;
         }
         int bubbleCount = mLayout.getChildCount();
+        final float totalBubbleWidth = bubbleCount * mBubbleSize;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSize;
-        float gap = (bubbleCount - 1) * mBubblePadding;
-        float row = gap + (float) bubble;
+        final float centerScreen = mDisplayWidth / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = centerScreen - halfRow;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplayWidth / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        return rowLeft;
+    }
 
-        return rowLeftFromScreenLeft;
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = mDisplayWidth - rowMargins;
+
+        final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mMaxBubbles - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
index f8b32c2..498330c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -38,6 +38,7 @@
 import com.google.android.collect.Sets;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
@@ -76,6 +77,7 @@
     }
 
     @Test
+    @Ignore
     public void testHierarchyChanges() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -101,6 +103,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateValueNotChained() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -127,11 +130,13 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateValueXChained() throws InterruptedException {
         testChainedTranslationAnimations();
     }
 
     @Test
+    @Ignore
     public void testSetEndActions() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -156,8 +161,8 @@
         });
 
         // Set end listeners for both x and y.
-        mLayout.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
-        mLayout.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
+        mTestableController.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -175,6 +180,7 @@
     }
 
     @Test
+    @Ignore
     public void testRemoveEndListeners() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -190,7 +196,7 @@
         });
 
         // Set the end listener.
-        mLayout.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -205,7 +211,7 @@
         mTestableController.animationForChildAtIndex(0)
                 .translationX(1000)
                 .start();
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        mTestableController.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
         xLatch.await(1, TimeUnit.SECONDS);
 
         // Make sure the end listener was not called.
@@ -213,6 +219,7 @@
     }
 
     @Test
+    @Ignore
     public void testSetController() throws InterruptedException {
         // Add the bubbles, then set the controller, to make sure that a controller added to an
         // already-initialized view works correctly.
@@ -269,6 +276,7 @@
     }
 
     @Test
+    @Ignore
     public void testArePropertiesAnimating() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -293,6 +301,7 @@
     }
 
     @Test
+    @Ignore
     public void testCancelAllAnimations() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -346,6 +355,7 @@
     }
 
     @Test
+    @Ignore
     public void testPhysicsAnimator() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -390,6 +400,7 @@
     }
 
     @Test
+    @Ignore
     public void testAnimationsForChildrenFromIndex() throws InterruptedException {
         // Don't chain since we're going to invoke each animation independently.
         mTestableController.setChainedProperties(new HashSet<>());
@@ -415,6 +426,7 @@
     }
 
     @Test
+    @Ignore
     public void testAnimationsForChildrenFromIndex_noChildren() {
         mLayout.setActiveController(mTestableController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index f633f39..a5f2e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -68,7 +68,7 @@
     @Mock
     private DisplayCutout mCutout;
 
-    private int mMaxBubbles;
+    protected int mMaxBubbles;
 
     @Before
     public void setUp() throws Exception {
@@ -195,14 +195,13 @@
         private void setTestEndActionForProperty(
                 Runnable action, DynamicAnimation.ViewProperty property) {
             final Runnable realEndAction = mEndActionForProperty.get(property);
-
-            setEndActionForProperty(() -> {
+            mLayout.mEndActionForProperty.put(property, () -> {
                 if (realEndAction != null) {
                     realEndAction.run();
                 }
 
                 action.run();
-            }, property);
+            });
         }
 
         /** PhysicsPropertyAnimator that posts its animations to the main thread. */
@@ -219,6 +218,11 @@
                         property, view, value, startVel, startDelay, stiffness, dampingRatio,
                         afterCallbacks));
             }
+
+            @Override
+            protected void startPathAnimation() {
+                mMainThreadHandler.post(super::startPathAnimation);
+            }
         }
 
         /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 31a7d5a..d79128c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -339,10 +339,10 @@
 
         @Override
         protected void springFirstBubbleWithStackFollowing(DynamicAnimation.ViewProperty property,
-                SpringForce spring, float vel, float finalPosition) {
+                SpringForce spring, float vel, float finalPosition, Runnable... after) {
             mMainThreadHandler.post(() ->
                     super.springFirstBubbleWithStackFollowing(
-                            property, spring, vel, finalPosition));
+                            property, spring, vel, finalPosition, after));
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index 7ea6493..3561e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -28,9 +28,13 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -43,61 +47,65 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class FalsingManagerProxyTest extends SysuiTestCase {
-    @Mock
+    @Mock(stubOnly = true)
     PluginManager mPluginManager;
-    private boolean mDefaultConfigValue;
+    @Mock(stubOnly = true)
+    ProximitySensor mProximitySensor;
     private Handler mHandler;
+    private FalsingManagerProxy mProxy;
+    private DeviceConfigProxy mDeviceConfig;
     private TestableLooper mTestableLooper;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mTestableLooper = TestableLooper.get(this);
         mHandler = new Handler(mTestableLooper.getLooper());
-        mDefaultConfigValue = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, false);
-        // In case it runs on a device where it's been set to true, set it to false by hand.
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+        mDeviceConfig = new DeviceConfigProxyFake();
+        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
     }
 
     @After
     public void tearDown() {
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, mDefaultConfigValue ? "true" : "false", false);
+        if (mProxy != null) {
+            mProxy.cleanup();
+        }
     }
 
     @Test
     public void test_brightLineFalsingManagerDisabled() {
-        FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
-
-        assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
+        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
     }
 
     @Test
-    public void test_brightLineFalsingManagerEnabled() {
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
-        FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
-
-        assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
-    }
-
-    @Test
-    public void test_brightLineFalsingManagerToggled() {
-        FalsingManagerProxy proxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler);
-        assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+    public void test_brightLineFalsingManagerEnabled() throws InterruptedException {
+        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
         mTestableLooper.processAllMessages();
-        proxy.setupFalsingManager(getContext());
-        assertThat(proxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
+        assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
+    }
 
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+    @Test
+    public void test_brightLineFalsingManagerToggled() throws InterruptedException {
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
+        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
+
+        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
+        mTestableLooper.processAllMessages();
+        assertThat(mProxy.getInternalFalsingManager(),
+                instanceOf(BrightLineFalsingManager.class));
+
+        mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_MANAGER_ENABLED, "false", false);
         mTestableLooper.processAllMessages();
-        proxy.setupFalsingManager(getContext());
-        assertThat(proxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
+        assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java
index b45d3f2..afe4200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DiagonalClassifierTest.java
@@ -28,6 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.util.DeviceConfigProxyFake;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -58,7 +60,7 @@
     public void setup() {
         super.setup();
         MockitoAnnotations.initMocks(this);
-        mClassifier = new DiagonalClassifier(mDataProvider);
+        mClassifier = new DiagonalClassifier(mDataProvider, new DeviceConfigProxyFake());
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DistanceClassifierTest.java
index 805bb91..f0f5fc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DistanceClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/DistanceClassifierTest.java
@@ -24,6 +24,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.util.DeviceConfigProxyFake;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,7 +43,7 @@
     public void setup() {
         super.setup();
         mDataProvider = getDataProvider();
-        mClassifier = new DistanceClassifier(mDataProvider);
+        mClassifier = new DistanceClassifier(mDataProvider, new DeviceConfigProxyFake());
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
index a6cabbf..35d59c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java
@@ -23,24 +23,22 @@
 import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.when;
 
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.sensors.ProximitySensor;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.lang.reflect.Field;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -60,7 +58,8 @@
         MockitoAnnotations.initMocks(this);
         when(mDataProvider.getInteractionType()).thenReturn(GENERIC);
         when(mDistanceClassifier.isLongSwipe()).thenReturn(false);
-        mClassifier = new ProximityClassifier(mDistanceClassifier, mDataProvider);
+        mClassifier = new ProximityClassifier(
+                mDistanceClassifier, mDataProvider, new DeviceConfigProxyFake());
     }
 
     @After
@@ -78,8 +77,8 @@
     @Test
     public void testPass_mostlyUncovered() {
         touchDown();
-        mClassifier.onSensorEvent(createSensorEvent(true, 1));
-        mClassifier.onSensorEvent(createSensorEvent(false, 2));
+        mClassifier.onProximityEvent(createSensorEvent(true, 1));
+        mClassifier.onProximityEvent(createSensorEvent(false, 2));
         touchUp(20);
         assertThat(mClassifier.isFalseTouch(), is(false));
     }
@@ -88,8 +87,8 @@
     public void testPass_quickSettings() {
         touchDown();
         when(mDataProvider.getInteractionType()).thenReturn(QUICK_SETTINGS);
-        mClassifier.onSensorEvent(createSensorEvent(true, 1));
-        mClassifier.onSensorEvent(createSensorEvent(false, 11));
+        mClassifier.onProximityEvent(createSensorEvent(true, 1));
+        mClassifier.onProximityEvent(createSensorEvent(false, 11));
         touchUp(10);
         assertThat(mClassifier.isFalseTouch(), is(false));
     }
@@ -97,8 +96,8 @@
     @Test
     public void testFail_covered() {
         touchDown();
-        mClassifier.onSensorEvent(createSensorEvent(true, 1));
-        mClassifier.onSensorEvent(createSensorEvent(false, 11));
+        mClassifier.onProximityEvent(createSensorEvent(true, 1));
+        mClassifier.onProximityEvent(createSensorEvent(false, 11));
         touchUp(10);
         assertThat(mClassifier.isFalseTouch(), is(true));
     }
@@ -106,10 +105,10 @@
     @Test
     public void testFail_mostlyCovered() {
         touchDown();
-        mClassifier.onSensorEvent(createSensorEvent(true, 1));
-        mClassifier.onSensorEvent(createSensorEvent(true, 95));
-        mClassifier.onSensorEvent(createSensorEvent(true, 96));
-        mClassifier.onSensorEvent(createSensorEvent(false, 100));
+        mClassifier.onProximityEvent(createSensorEvent(true, 1));
+        mClassifier.onProximityEvent(createSensorEvent(true, 95));
+        mClassifier.onProximityEvent(createSensorEvent(true, 96));
+        mClassifier.onProximityEvent(createSensorEvent(false, 100));
         touchUp(100);
         assertThat(mClassifier.isFalseTouch(), is(true));
     }
@@ -117,8 +116,8 @@
     @Test
     public void testPass_coveredWithLongSwipe() {
         touchDown();
-        mClassifier.onSensorEvent(createSensorEvent(true, 1));
-        mClassifier.onSensorEvent(createSensorEvent(false, 11));
+        mClassifier.onProximityEvent(createSensorEvent(true, 1));
+        mClassifier.onProximityEvent(createSensorEvent(false, 11));
         touchUp(10);
         when(mDistanceClassifier.isLongSwipe()).thenReturn(true);
         assertThat(mClassifier.isFalseTouch(), is(false));
@@ -139,26 +138,7 @@
         motionEvent.recycle();
     }
 
-    private SensorEvent createSensorEvent(boolean covered, long timestampMs) {
-        SensorEvent sensorEvent = Mockito.mock(SensorEvent.class);
-        Sensor sensor = Mockito.mock(Sensor.class);
-        when(sensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY);
-        when(sensor.getMaximumRange()).thenReturn(1f);
-        sensorEvent.sensor = sensor;
-        sensorEvent.timestamp = timestampMs * NS_PER_MS;
-        try {
-            Field valuesField = SensorEvent.class.getField("values");
-            valuesField.setAccessible(true);
-            float[] sensorValue = {covered ? 0 : 1};
-            try {
-                valuesField.set(sensorEvent, sensorValue);
-            } catch (IllegalAccessException e) {
-                e.printStackTrace();
-            }
-        } catch (NoSuchFieldException e) {
-            e.printStackTrace();
-        }
-
-        return sensorEvent;
+    private ProximitySensor.ProximityEvent createSensorEvent(boolean covered, long timestampMs) {
+        return new ProximitySensor.ProximityEvent(covered, timestampMs * NS_PER_MS);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ZigZagClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ZigZagClassifierTest.java
index fb4c1ec..387c0da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ZigZagClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ZigZagClassifierTest.java
@@ -24,6 +24,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.util.DeviceConfigProxyFake;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -41,7 +43,7 @@
     @Before
     public void setup() {
         super.setup();
-        mClassifier = new ZigZagClassifier(getDataProvider());
+        mClassifier = new ZigZagClassifier(getDataProvider(), new DeviceConfigProxyFake());
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 0c124ff..752e145 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -24,7 +24,7 @@
 import static org.mockito.Mockito.withSettings;
 
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
 
 import org.mockito.Answers;
 import org.mockito.MockSettings;
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 392c677..aa62e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -40,7 +40,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.utils.hardware.FakeSensorManager;
+import com.android.systemui.util.sensors.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index cd6d1e0..ddd1685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -44,7 +44,7 @@
 import com.android.systemui.doze.DozeSensors.TriggerSensor;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e190f99..b0e3969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -32,6 +32,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -39,9 +40,12 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.FakeProximitySensor;
+import com.android.systemui.util.sensors.FakeSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.util.wakelock.WakeLockFake;
-import com.android.systemui.utils.hardware.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -55,13 +59,10 @@
     private DozeTriggers mTriggers;
     private DozeMachine mMachine;
     private DozeHostFake mHost;
-    private AmbientDisplayConfiguration mConfig;
-    private DozeParameters mParameters;
     private FakeSensorManager mSensors;
     private Sensor mTapSensor;
-    private WakeLock mWakeLock;
-    private AlarmManager mAlarmManager;
     private DockManager mDockManagerFake;
+    private FakeProximitySensor mProximitySensor;
 
     @BeforeClass
     public static void setupSuite() {
@@ -72,18 +73,22 @@
     @Before
     public void setUp() throws Exception {
         mMachine = mock(DozeMachine.class);
-        mAlarmManager = mock(AlarmManager.class);
+        AlarmManager alarmManager = mock(AlarmManager.class);
         mHost = spy(new DozeHostFake());
-        mConfig = DozeConfigurationUtil.createMockConfig();
-        mParameters = DozeConfigurationUtil.createMockParameters();
+        AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
+        DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
         mSensors = spy(new FakeSensorManager(mContext));
         mTapSensor = mSensors.getFakeTapSensor().getSensor();
-        mWakeLock = new WakeLockFake();
+        WakeLock wakeLock = new WakeLockFake();
         mDockManagerFake = mock(DockManager.class);
+        AsyncSensorManager asyncSensorManager =
+                new AsyncSensorManager(mSensors, null, new Handler());
+        mProximitySensor = new FakeProximitySensor(getContext(), asyncSensorManager);
 
-        mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters,
-                mSensors, Handler.createAsync(Looper.myLooper()), mWakeLock, true,
-                mDockManagerFake);
+        mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+                asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
+                mDockManagerFake, mProximitySensor);
+        waitForSensorManager();
     }
 
     @Test
@@ -94,14 +99,17 @@
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
         clearInvocations(mMachine);
 
+        mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1));
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
-        mSensors.getMockProximitySensor().sendProximityResult(false); /* Near */
+        mProximitySensor.alertListeners();
 
         verify(mMachine, never()).requestState(any());
         verify(mMachine, never()).requestPulse(anyInt());
 
         mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
-        mSensors.getMockProximitySensor().sendProximityResult(true); /* Far */
+        waitForSensorManager();
+        mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
+        mProximitySensor.alertListeners();
 
         verify(mMachine).requestPulse(anyInt());
     }
@@ -111,6 +119,7 @@
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
 
         mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
+        waitForSensorManager();
         verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
 
         clearInvocations(mSensors);
@@ -118,10 +127,12 @@
                 DozeMachine.State.DOZE_REQUEST_PULSE);
         mTriggers.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
                 DozeMachine.State.DOZE_PULSING);
+        waitForSensorManager();
         verify(mSensors).cancelTriggerSensor(any(), eq(mTapSensor));
 
         clearInvocations(mSensors);
         mTriggers.transitionTo(DozeMachine.State.DOZE_PULSING, DozeMachine.State.DOZE_PULSE_DONE);
+        waitForSensorManager();
         verify(mSensors).requestTriggerSensor(any(), eq(mTapSensor));
     }
 
@@ -133,4 +144,16 @@
         mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
         verify(mDockManagerFake).removeListener(any());
     }
+
+    @Test
+    public void testProximitySensorNotAvailablel() {
+        mProximitySensor.setSensorAvailable(false);
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null);
+        mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100, new float[]{1});
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null);
+    }
+
+    private void waitForSensorManager() {
+        TestableLooper.get(this).processAllMessages();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
index d35fc30..7fa1dbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
@@ -38,12 +38,13 @@
 @RunWith(AndroidJUnit4.class)
 public class DismissCallbackRegistryTest extends SysuiTestCase {
 
-    private final DismissCallbackRegistry mDismissCallbackRegistry = new DismissCallbackRegistry();
+    private DismissCallbackRegistry mDismissCallbackRegistry;
     private @Mock IKeyguardDismissCallback mMockCallback;
     private @Mock IKeyguardDismissCallback mMockCallback2;
 
     @Before
     public void setUp() throws Exception {
+        mDismissCallbackRegistry =  new DismissCallbackRegistry();
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 9576cb2..c9b6790 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -85,15 +86,15 @@
     @Mock
     private SettableWakeLock mMediaWakeLock;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
     private DozeParameters mDozeParameters;
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private TestableKeyguardSliceProvider mProvider;
     private boolean mIsZenMode;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mIsZenMode = false;
         mProvider = new TestableKeyguardSliceProvider();
         mProvider.attachInfo(getContext(), null);
@@ -102,6 +103,11 @@
         SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
     }
 
+    @After
+    public void tearDown() {
+        mProvider.onDestroy();
+    }
+
     @Test
     public void registersClockUpdate() {
         Assert.assertTrue("registerClockUpdate should have been called during initialization.",
@@ -269,11 +275,6 @@
         }
 
         @Override
-        public KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
-            return mKeyguardUpdateMonitor;
-        }
-
-        @Override
         protected String getFormattedDateLocked() {
             return super.getFormattedDateLocked() + mCounter++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index f2292fd..07fbbcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -97,6 +97,8 @@
                 mLooper.getLooper(),
                 mPluginManager, mTunerService, mAutoTiles, mDumpController);
         setUpTileFactory();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
+                "", ActivityManager.getCurrentUser());
     }
 
     private void setUpTileFactory() {
@@ -188,7 +190,8 @@
             // changed
             String newSetting = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                     TILES_SETTING, ActivityManager.getCurrentUser());
-            if (!previousSetting.equals(newSetting)) {
+            // newSetting is not null, as it has just been set.
+            if (!newSetting.equals(previousSetting)) {
                 onTuningChanged(TILES_SETTING, newSetting);
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 818db87..853b2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -40,7 +40,7 @@
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.NetworkController;
 
 import org.junit.Before;
@@ -64,7 +64,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private KeyguardMonitor mKeyguard;
+    private KeyguardStateController mKeyguard;
     @Mock
     private NetworkController mNetworkController;
     @Mock
@@ -83,7 +83,7 @@
         mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
         mController = mDependency.injectMockDependency(CastController.class);
         mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
-        mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class);
+        mKeyguard = mDependency.injectMockDependency(KeyguardStateController.class);
         mNetworkController = mDependency.injectMockDependency(NetworkController.class);
 
         when(mHost.getContext()).thenReturn(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
new file mode 100644
index 0000000..b51e716
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.qs.tiles
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UserDetailViewAdapterTest : SysuiTestCase() {
+
+    @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+    @Mock private lateinit var mParent: ViewGroup
+    @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mOtherView: View
+    @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mUserInfo: UserInfo
+    @Mock private lateinit var mPicture: Bitmap
+    @Mock private lateinit var mLayoutInflater: LayoutInflater
+    private lateinit var adapter: UserDetailView.Adapter
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
+        `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
+                .thenReturn(mInflatedUserDetailItemView)
+        adapter = UserDetailView.Adapter(mContext, mUserSwitcherController)
+    }
+
+    private fun clickableTest(
+        current: Boolean,
+        guest: Boolean,
+        convertView: View,
+        shouldBeClickable: Boolean
+    ) {
+        val user = createUserRecord(current, guest)
+        val v = adapter.createUserDetailItemView(convertView, mParent, user)
+        if (shouldBeClickable) {
+            verify(v).setOnClickListener(adapter)
+        } else {
+            verify(v).setOnClickListener(null)
+        }
+    }
+
+    @Test
+    fun testGuestIsClickable_differentViews_notCurrent() {
+        clickableTest(false, true, mOtherView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_differentViews_Current() {
+        clickableTest(true, true, mOtherView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_sameView_notCurrent() {
+        clickableTest(false, true, mUserDetailItemView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_sameView_Current() {
+        clickableTest(true, true, mUserDetailItemView, true)
+    }
+
+    @Test
+    fun testNotGuestCurrentUserIsNotClickable_otherView() {
+        clickableTest(true, false, mOtherView, false)
+    }
+
+    @Test
+    fun testNotGuestCurrentUserIsNotClickable_sameView() {
+        clickableTest(true, false, mUserDetailItemView, false)
+    }
+
+    @Test
+    fun testNotGuestNotCurrentUserIsClickable_otherView() {
+        clickableTest(false, false, mOtherView, true)
+    }
+
+    @Test
+    fun testNotGuestNotCurrentUserIsClickable_sameView() {
+        clickableTest(false, false, mUserDetailItemView, true)
+    }
+
+    private fun createUserRecord(current: Boolean, guest: Boolean) =
+            UserSwitcherController.UserRecord(
+                    mUserInfo,
+                    mPicture,
+                    guest,
+                    current,
+                    false /* isAddUser */,
+                    false /* isRestricted */,
+                    true /* isSwitchToEnabled */)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
index 784d035..0b871e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
@@ -15,11 +15,11 @@
  */
 package com.android.systemui.screenshot;
 
-import com.android.systemui.R;
-
 import android.app.Activity;
 import android.os.Bundle;
 
+import com.android.systemui.tests.R;
+
 /**
  * A stub activity used in {@link ScreenshotTest}.
  */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 881cc39..cf5a12f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -129,7 +129,9 @@
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
         mSbn = createNewNotification(0 /* id */);
-        mEntry = new NotificationEntry(mSbn);
+        mEntry = new NotificationEntryBuilder()
+                .setSbn(mSbn)
+                .build();
         mEntry.setRow(mRow);
 
         mAlertingNotificationManager = createAlertingNotificationManager();
@@ -180,7 +182,9 @@
     public void testReleaseAllImmediately() {
         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
             StatusBarNotification sbn = createNewNotification(i);
-            NotificationEntry entry = new NotificationEntry(sbn);
+            NotificationEntry entry = new NotificationEntryBuilder()
+                    .setSbn(sbn)
+                    .build();
             entry.setRow(mRow);
             mAlertingNotificationManager.showNotification(entry);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index daee55b..cf6fd4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
@@ -46,15 +45,14 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
 import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.Before;
@@ -68,17 +66,11 @@
 @RunWith(AndroidJUnit4.class)
 public class KeyguardIndicationControllerTest extends SysuiTestCase {
 
-    private final String ORGANIZATION_NAME = "organization";
-
-    private String mDisclosureWithOrganization;
-
     @Mock
     private DevicePolicyManager mDevicePolicyManager;
     @Mock
     private ViewGroup mIndicationArea;
     @Mock
-    private KeyguardIndicationTextView mDisclosure;
-    @Mock
     private LockIcon mLockIcon;
     @Mock
     private LockPatternUtils mLockPatternUtils;
@@ -87,7 +79,7 @@
     @Mock
     private AccessibilityController mAccessibilityController;
     @Mock
-    private UnlockMethodCache mUnlockMethodCache;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
@@ -107,11 +99,7 @@
         mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
-        mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
-                ORGANIZATION_NAME);
 
-        when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure))
-                .thenReturn(mDisclosure);
         when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
 
         mWakeLock = new WakeLockFake();
@@ -123,73 +111,7 @@
         }
         mController = new KeyguardIndicationController(mContext, mIndicationArea, mLockIcon,
                 mLockPatternUtils, mWakeLock, mShadeController, mAccessibilityController,
-                mUnlockMethodCache, mStatusBarStateController, mKeyguardUpdateMonitor);
-    }
-
-    @Test
-    public void unmanaged() {
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        createController();
-
-        verify(mDisclosure).setVisibility(View.GONE);
-        verifyNoMoreInteractions(mDisclosure);
-    }
-
-    @Test
-    public void managedNoOwnerName() {
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
-        createController();
-
-        verify(mDisclosure).setVisibility(View.VISIBLE);
-        verify(mDisclosure).switchIndication(R.string.do_disclosure_generic);
-        verifyNoMoreInteractions(mDisclosure);
-    }
-
-    @Test
-    public void managedOwnerName() {
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
-        createController();
-
-        verify(mDisclosure).setVisibility(View.VISIBLE);
-        verify(mDisclosure).switchIndication(mDisclosureWithOrganization);
-        verifyNoMoreInteractions(mDisclosure);
-    }
-
-    @Test
-    public void updateOnTheFly() {
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        createController();
-
-        final KeyguardUpdateMonitorCallback monitor = mController.getKeyguardCallback();
-        reset(mDisclosure);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
-        monitor.onKeyguardVisibilityChanged(true);
-
-        verify(mDisclosure).setVisibility(View.VISIBLE);
-        verify(mDisclosure).switchIndication(R.string.do_disclosure_generic);
-        verifyNoMoreInteractions(mDisclosure);
-        reset(mDisclosure);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
-        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
-        monitor.onKeyguardVisibilityChanged(false);
-        monitor.onKeyguardVisibilityChanged(true);
-
-        verify(mDisclosure).setVisibility(View.VISIBLE);
-        verify(mDisclosure).switchIndication(mDisclosureWithOrganization);
-        verifyNoMoreInteractions(mDisclosure);
-        reset(mDisclosure);
-
-        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
-        monitor.onKeyguardVisibilityChanged(false);
-        monitor.onKeyguardVisibilityChanged(true);
-
-        verify(mDisclosure).setVisibility(View.GONE);
-        verifyNoMoreInteractions(mDisclosure);
+                mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor);
     }
 
     @Test
@@ -265,24 +187,30 @@
     }
 
     @Test
-    public void unlockMethodCache_listenerUpdatesIndication() {
+    public void updateMonitor_listenerUpdatesIndication() {
         createController();
         String restingIndication = "Resting indication";
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
-        mController.setRestingIndication(restingIndication);
+
         mController.setVisible(true);
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getString(com.android.internal.R.string.lockscreen_storage_locked));
+
+        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
+        mController.setRestingIndication(restingIndication);
         assertThat(mTextView.getText()).isEqualTo(mController.getTrustGrantedIndication());
 
         reset(mKeyguardUpdateMonitor);
+        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
         when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        mController.onUnlockMethodStateChanged();
+        mController.onUnlockedChanged();
         assertThat(mTextView.getText()).isEqualTo(restingIndication);
     }
 
     @Test
-    public void unlockMethodCache_listener() {
+    public void updateMonitor_listener() {
         createController();
-        verify(mUnlockMethodCache).addListener(eq(mController));
+        verify(mKeyguardStateController).addCallback(eq(mController));
         verify(mStatusBarStateController).addCallback(eq(mController));
         verify(mKeyguardUpdateMonitor, times(2)).registerCallback(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 690133a..a0a410d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -24,6 +24,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
@@ -66,6 +67,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                new Handler(TestableLooper.get(this).getLooper()));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
new file mode 100644
index 0000000..fcfdd11
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
@@ -0,0 +1,209 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Importance;
+import android.os.UserHandle;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Combined builder for constructing a NotificationEntry and its associated StatusBarNotification
+ * and Ranking. Is largely a proxy for the SBN and Ranking builders, but does a little extra magic
+ * to make sure the keys match between the two, etc.
+ *
+ * Only for use in tests.
+ */
+public class NotificationEntryBuilder {
+    private final SbnBuilder mSbnBuilder = new SbnBuilder();
+    private final RankingBuilder mRankingBuilder = new RankingBuilder();
+    private StatusBarNotification mSbn = null;
+
+    public NotificationEntry build() {
+        StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
+        mRankingBuilder.setKey(sbn.getKey());
+        return new NotificationEntry(sbn, mRankingBuilder.build());
+    }
+
+    /**
+     * Sets the SBN directly. If set, causes all calls to delegated SbnBuilder methods to be
+     * ignored.
+     */
+    public NotificationEntryBuilder setSbn(@Nullable StatusBarNotification sbn) {
+        mSbn = sbn;
+        return this;
+    }
+
+    /* Delegated to SbnBuilder */
+
+    public NotificationEntryBuilder setPkg(String pkg) {
+        mSbnBuilder.setPkg(pkg);
+        return this;
+    }
+
+    public NotificationEntryBuilder setOpPkg(String opPkg) {
+        mSbnBuilder.setOpPkg(opPkg);
+        return this;
+    }
+
+    public NotificationEntryBuilder setId(int id) {
+        mSbnBuilder.setId(id);
+        return this;
+    }
+
+    public NotificationEntryBuilder setTag(String tag) {
+        mSbnBuilder.setTag(tag);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUid(int uid) {
+        mSbnBuilder.setUid(uid);
+        return this;
+    }
+
+    public NotificationEntryBuilder setInitialPid(int initialPid) {
+        mSbnBuilder.setInitialPid(initialPid);
+        return this;
+    }
+
+    public NotificationEntryBuilder setNotification(Notification notification) {
+        mSbnBuilder.setNotification(notification);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUser(UserHandle user) {
+        mSbnBuilder.setUser(user);
+        return this;
+    }
+
+    public NotificationEntryBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mSbnBuilder.setOverrideGroupKey(overrideGroupKey);
+        return this;
+    }
+
+    public NotificationEntryBuilder setPostTime(long postTime) {
+        mSbnBuilder.setPostTime(postTime);
+        return this;
+    }
+
+    /* Delegated to RankingBuilder */
+
+    public NotificationEntryBuilder setRank(int rank) {
+        mRankingBuilder.setRank(rank);
+        return this;
+    }
+
+    public NotificationEntryBuilder setMatchesInterruptionFilter(
+            boolean matchesInterruptionFilter) {
+        mRankingBuilder.setMatchesInterruptionFilter(matchesInterruptionFilter);
+        return this;
+    }
+
+    public NotificationEntryBuilder setVisibilityOverride(int visibilityOverride) {
+        mRankingBuilder.setVisibilityOverride(visibilityOverride);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSuppressedVisualEffects(int suppressedVisualEffects) {
+        mRankingBuilder.setSuppressedVisualEffects(suppressedVisualEffects);
+        return this;
+    }
+
+    public NotificationEntryBuilder setExplanation(CharSequence explanation) {
+        mRankingBuilder.setExplanation(explanation);
+        return this;
+    }
+
+    public NotificationEntryBuilder setAdditionalPeople(ArrayList<String> additionalPeople) {
+        mRankingBuilder.setAdditionalPeople(additionalPeople);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSnoozeCriteria(
+            ArrayList<SnoozeCriterion> snoozeCriteria) {
+        mRankingBuilder.setSnoozeCriteria(snoozeCriteria);
+        return this;
+    }
+
+    public NotificationEntryBuilder setCanShowBadge(boolean canShowBadge) {
+        mRankingBuilder.setCanShowBadge(canShowBadge);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSuspended(boolean suspended) {
+        mRankingBuilder.setSuspended(suspended);
+        return this;
+    }
+
+    public NotificationEntryBuilder setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
+        mRankingBuilder.setLastAudiblyAlertedMs(lastAudiblyAlertedMs);
+        return this;
+    }
+
+    public NotificationEntryBuilder setNoisy(boolean noisy) {
+        mRankingBuilder.setNoisy(noisy);
+        return this;
+    }
+
+    public NotificationEntryBuilder setCanBubble(boolean canBubble) {
+        mRankingBuilder.setCanBubble(canBubble);
+        return this;
+    }
+
+    public NotificationEntryBuilder setImportance(@Importance int importance) {
+        mRankingBuilder.setImportance(importance);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUserSentiment(int userSentiment) {
+        mRankingBuilder.setUserSentiment(userSentiment);
+        return this;
+    }
+
+    public NotificationEntryBuilder setChannel(NotificationChannel channel) {
+        mRankingBuilder.setChannel(channel);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartActions(
+            ArrayList<Notification.Action> smartActions) {
+        mRankingBuilder.setSmartActions(smartActions);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartActions(Notification.Action... smartActions) {
+        mRankingBuilder.setSmartActions(smartActions);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartReplies(ArrayList<CharSequence> smartReplies) {
+        mRankingBuilder.setSmartReplies(smartReplies);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartReplies(CharSequence... smartReplies) {
+        mRankingBuilder.setSmartReplies(smartReplies);
+        return this;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
new file mode 100644
index 0000000..33b0d2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+@VisibleForTesting
+public class NotificationEntryHelper {
+    public static ModifiedRankingBuilder modifyRanking(NotificationEntry entry) {
+        return new ModifiedRankingBuilder(entry);
+    }
+
+    public static ModifiedSbnBuilder modifySbn(NotificationEntry entry) {
+        return new ModifiedSbnBuilder(entry);
+    }
+
+    public static class ModifiedRankingBuilder extends RankingBuilder {
+        private final NotificationEntry mTarget;
+
+        private ModifiedRankingBuilder(NotificationEntry target) {
+            super(target.ranking());
+            mTarget = target;
+        }
+
+        @Override
+        public Ranking build() {
+            final Ranking ranking = super.build();
+            mTarget.setRanking(ranking);
+            return ranking;
+        }
+    }
+
+    public static class ModifiedSbnBuilder extends SbnBuilder {
+        private final NotificationEntry mTarget;
+
+        private ModifiedSbnBuilder(NotificationEntry target) {
+            super(target.sbn());
+            mTarget = target;
+        }
+
+        @Override
+        public StatusBarNotification build() {
+            final StatusBarNotification sbn = super.build();
+            mTarget.setNotification(sbn);
+            return sbn;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
new file mode 100644
index 0000000..350ab5a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -0,0 +1,606 @@
+/*
+ * 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;
+
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.service.dreams.IDreamManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the interruption state provider which understands whether the system & notification
+ * is in a state allowing a particular notification to hun, pulse, or bubble.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
+
+    @Mock
+    PowerManager mPowerManager;
+    @Mock
+    IDreamManager mDreamManager;
+    @Mock
+    AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    NotificationFilter mNotificationFilter;
+    @Mock
+    StatusBarStateController mStatusBarStateController;
+    @Mock
+    NotificationPresenter mPresenter;
+    @Mock
+    HeadsUpManager mHeadsUpManager;
+    @Mock
+    NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+
+    private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mNotifInterruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mPowerManager,
+                        mDreamManager,
+                        mAmbientDisplayConfiguration,
+                        mNotificationFilter,
+                        mStatusBarStateController);
+
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills group suppression check.
+     */
+    private void ensureStateForAlertCommon() {
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills launch fullscreen check.
+     */
+    private void ensureStateForAlertAwakeCommon() {
+        when(mPresenter.isDeviceInVrMode()).thenReturn(false);
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenDozing() {
+        ensureStateForAlertCommon();
+
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & bubble checks.
+     */
+    private void ensureStateForBubbleUp() {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+    }
+
+    /**
+     * Ensure that the disabled state is set correctly.
+     */
+    @Test
+    public void testDisableNotificationAlerts() {
+        // Enabled by default
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+
+        // Disable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue();
+
+        // Enable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(false);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+    }
+
+    /**
+     * Ensure that the disabled alert state effects whether HUNs are enabled.
+     */
+    @Test
+    public void testHunSettingsChange_enabled_butAlertsDisabled() {
+        // Set up but without a mock change observer
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+
+        // HUNs enabled by default
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue();
+
+        // Set alerts disabled
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+
+        // No more HUNs
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+    }
+
+    /**
+     * Alerts can happen.
+     */
+    @Test
+    public void testCanAlertCommon_true() {
+        ensureStateForAlertCommon();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
+    }
+
+    /**
+     * Filtered out notifications don't alert.
+     */
+    @Test
+    public void testCanAlertCommon_false_filteredOut() {
+        ensureStateForAlertCommon();
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+    }
+
+    /**
+     * Grouped notifications have different alerting behaviours, sometimes the alert for a
+     * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}.
+     */
+    @Test
+    public void testCanAlertCommon_false_suppressedForGroups() {
+        ensureStateForAlertCommon();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(new Notification.Builder(getContext(), "a")
+                        .setGroup("a")
+                        .setGroupSummary(true)
+                        .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+                        .build())
+                .setImportance(IMPORTANCE_DEFAULT)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+    }
+
+    /**
+     * HUNs while dozing can happen.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_true() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    /**
+     * Ambient display can show HUNs for new notifications, this may be disabled.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_pulseDisabled() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is not in ambient display or sleeping then we don't HUN.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_notDozing() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND ambient effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+     * get to pulse.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_lessImportant() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_LOW);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Heads up can happen.
+     */
+    @Test
+    public void testShouldHeadsUp_true() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    /**
+     * Heads up notifications can be disabled in general.
+     */
+    @Test
+    public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Set alerts disabled, this should cause heads up to be false
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is dozing, we don't show as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_dozing() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification is a bubble, and the user is not on AOD / lockscreen, then
+     * the bubble is shown rather than the heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_bubble() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Bubble bit only applies to interruption when we're in the shade
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isFalse();
+    }
+
+    /**
+     * If we're not allowed to alert in general, we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND HUN peek effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
+     */
+    @Test
+    public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_HIGH} don't get
+     * to show as a heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is not in use then we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+        // Device is not in use if screen is not on
+        when(mPowerManager.isScreenOn()).thenReturn(false);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+        // Also not in use if screen is on but we're showing screen saver / "dreaming"
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If something wants to suppress this heads up, then it shouldn't be shown as a heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_suppressed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        verify(mHeadsUpSuppressor).canHeadsUp(any(), any());
+    }
+
+    /**
+     * On screen alerts don't happen when the device is in VR Mode.
+     */
+    @Test
+    public void testCanAlertAwakeCommon__false_vrMode() {
+        ensureStateForAlertAwakeCommon();
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * On screen alerts don't happen when the notification is snoozed.
+     */
+    @Test
+    public void testCanAlertAwakeCommon_false_snoozedPackage() {
+        ensureStateForAlertAwakeCommon();
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * On screen alerts don't happen when that package has just launched fullscreen.
+     */
+    @Test
+    public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
+        ensureStateForAlertAwakeCommon();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        entry.notifyFullScreenIntentLaunched();
+
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * Bubbles can happen.
+     */
+    @Test
+    public void testShouldBubbleUp_true() {
+        ensureStateForBubbleUp();
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
+    }
+
+    /**
+     * If the notification doesn't have permission to bubble, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notAllowedToBubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createBubble();
+        modifyRanking(entry)
+                .setCanBubble(false)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification isn't a bubble, it should definitely not show as a bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notABubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification doesn't have bubble metadata, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_invalidMetadata() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
+        entry.sbn().getNotification().flags |= FLAG_BUBBLE;
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_alertAwakeCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // Make alert common return false by pretending we're in VR mode
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+    }
+
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_alertCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+    }
+
+    private NotificationEntry createBubble() {
+        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+                .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
+                .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                .build();
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .setBubbleMetadata(data)
+                .build();
+        n.flags |= FLAG_BUBBLE;
+
+        return new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .setCanBubble(true)
+                .build();
+    }
+
+    private NotificationEntry createNotification(int importance) {
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .build();
+
+        return new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(importance)
+                .build();
+    }
+
+    /**
+     * Testable class overriding constructor.
+     */
+    public class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
+
+        TestableNotificationInterruptionStateProvider(Context context,
+                PowerManager powerManager, IDreamManager dreamManager,
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter notificationFilter,
+                StatusBarStateController statusBarStateController) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+                    notificationFilter,
+                    statusBarStateController);
+        }
+    }
+}
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 0800cb9..86869bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -38,7 +38,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -91,7 +90,11 @@
 
     @Test
     public void testNotificationUpdateCallsUpdateNotification() {
-        when(mNotificationData.get(mSbn.getKey())).thenReturn(new NotificationEntry(mSbn));
+        when(mNotificationData.get(mSbn.getKey()))
+                .thenReturn(
+                        new NotificationEntryBuilder()
+                                .setSbn(mSbn)
+                                .build());
         mListener.onNotificationPosted(mSbn, mRanking);
         TestableLooper.get(this).processAllMessages();
         verify(mEntryManager).updateNotification(mSbn, mRanking);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 57dd8c9..a027643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -19,11 +19,12 @@
 import static android.content.Intent.ACTION_USER_SWITCHED;
 import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -166,7 +167,10 @@
                 Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
         when(mNotificationData.isHighPriority(any())).thenReturn(false);
 
-        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(mock(NotificationEntry.class)));
+        NotificationEntry entry = new NotificationEntryBuilder().build();
+        entry.setBucket(BUCKET_SILENT);
+
+        assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry));
     }
 
     @Test
@@ -179,7 +183,9 @@
                 Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
         when(mNotificationData.isHighPriority(any())).thenReturn(false);
 
-        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(mock(NotificationEntry.class)));
+        NotificationEntry entry = new NotificationEntryBuilder().build();
+        entry.setBucket(BUCKET_SILENT);
+        assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
     }
 
     private class TestNotificationLockscreenUserManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index da25eed..852ddb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -30,7 +30,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ShadeController;
 
 import com.google.android.collect.Sets;
@@ -64,7 +63,6 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
 
     private TestableNotificationRemoteInputManager mRemoteInputManager;
-    private StatusBarNotification mSbn;
     private NotificationEntry mEntry;
     private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
     private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
@@ -79,9 +77,13 @@
                 () -> mock(ShadeController.class),
                 mStateController,
                 Handler.createAsync(Looper.myLooper()));
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
-                0, new Notification(), UserHandle.CURRENT, null, 0);
-        mEntry = new NotificationEntry(mSbn);
+        mEntry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setNotification(new Notification())
+                .setUser(UserHandle.CURRENT)
+                .build();
         mEntry.setRow(mRow);
 
         mRemoteInputManager.setUpWithPresenterForTest(mCallback,
@@ -95,7 +97,7 @@
     @Test
     public void testPerformOnRemoveNotification() {
         when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
-        mRemoteInputManager.onPerformRemoveNotification(mEntry, mSbn.getKey());
+        mRemoteInputManager.onPerformRemoveNotification(mEntry, mEntry.key());
 
         verify(mController).removeRemoteInput(mEntry, null);
     }
@@ -176,7 +178,9 @@
         // Setup a notification entry with 1 remote input.
         StatusBarNotification newSbn =
                 mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false);
-        NotificationEntry entry = new NotificationEntry(newSbn);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSbn(newSbn)
+                .build();
 
         // Try rebuilding to add another reply.
         newSbn = mRemoteInputManager.rebuildNotificationWithRemoteInput(entry, "Reply 2", true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 7063ddf..de77af8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static org.mockito.Mockito.mock;
 
 import android.annotation.Nullable;
@@ -40,7 +42,6 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.systemui.R;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -50,7 +51,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.tests.R;
 
 /**
  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
@@ -188,7 +189,9 @@
         n.flags |= FLAG_BUBBLE;
         ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE,
                 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
-        row.getEntry().canBubble = true;
+        modifyRanking(row.getEntry())
+                .setCanBubble(true)
+                .build();
         return row;
     }
 
@@ -296,23 +299,28 @@
         row.setGroupManager(mGroupManager);
         row.setHeadsUpManager(mHeadsUpManager);
         row.setAboveShelfChangedListener(aboveShelf -> {});
-        StatusBarNotification sbn = new StatusBarNotification(
-                pkg,
-                pkg,
-                mId++,
-                null /* tag */,
-                uid,
-                2000 /* initialPid */,
-                notification,
-                userHandle,
-                null /* overrideGroupKey */,
-                System.currentTimeMillis());
-        NotificationEntry entry = new NotificationEntry(sbn);
+
+        final NotificationChannel channel =
+                new NotificationChannel(
+                        notification.getChannelId(),
+                        notification.getChannelId(),
+                        importance);
+        channel.setBlockableSystem(true);
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(pkg)
+                .setOpPkg(pkg)
+                .setId(mId++)
+                .setUid(uid)
+                .setInitialPid(2000)
+                .setNotification(notification)
+                .setUser(userHandle)
+                .setPostTime(System.currentTimeMillis())
+                .setChannel(channel)
+                .build();
+
         entry.setRow(row);
-        entry.createIcons(mContext, sbn);
-        entry.channel = new NotificationChannel(
-                notification.getChannelId(), notification.getChannelId(), importance);
-        entry.channel.setBlockableSystem(true);
+        entry.createIcons(mContext, entry.sbn());
         row.setEntry(entry);
         row.getNotificationInflater().addInflationFlags(extraInflationFlags);
         NotificationContentInflaterTest.runThenWaitForInflation(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index f63389c..d00be56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -30,6 +30,7 @@
 
 import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -199,11 +200,11 @@
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
             String key, List<Notification.Action> actions) {
-        return new NotificationUiAdjustment(key, actions, new CharSequence[0]);
+        return new NotificationUiAdjustment(key, actions, null);
     }
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
             String key, CharSequence[] replies) {
-        return new NotificationUiAdjustment(key, Collections.emptyList(), replies);
+        return new NotificationUiAdjustment(key, null, Arrays.asList(replies));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 58fb53a..9e72504 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -41,7 +41,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -110,8 +110,9 @@
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
-                () -> mShadeController, new BubbleData(mContext),
+                () -> mShadeController,
                 mock(KeyguardBypassController.class),
+                mock(BubbleController.class),
                 mock(DynamicPrivacyController.class));
         Dependency.get(InitController.class).executePostInitTasks();
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
@@ -119,7 +120,9 @@
 
     private NotificationEntry createEntry() throws Exception {
         ExpandableNotificationRow row = mHelper.createRow();
-        NotificationEntry entry = new NotificationEntry(row.getStatusBarNotification());
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSbn(row.getStatusBarNotification())
+                .build();
         entry.setRow(row);
         return entry;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
new file mode 100644
index 0000000..820f465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -0,0 +1,220 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Importance;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.SnoozeCriterion;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Standard builder class for Ranking objects. For use in tests that need to craft the underlying
+ * Ranking object of a NotificationEntry.
+ */
+public class RankingBuilder {
+    private String mKey = "test_key";
+    private int mRank = 0;
+    private boolean mMatchesInterruptionFilter = false;
+    private int mVisibilityOverride = 0;
+    private int mSuppressedVisualEffects = 0;
+    @Importance private int mImportance = 0;
+    private CharSequence mExplanation = "test_explanation";
+    private String mOverrideGroupKey = null;
+    private NotificationChannel mChannel = null;
+    private ArrayList<String> mAdditionalPeople = null;
+    private ArrayList<SnoozeCriterion> mSnoozeCriteria = null;
+    private boolean mCanShowBadge = false;
+    private int mUserSentiment = 0;
+    private boolean mIsSuspended = false;
+    private long mLastAudiblyAlertedMs = 0;
+    private boolean mNoisy = false;
+    private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
+    private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
+    private boolean mCanBubble = false;
+    private boolean mIsVisuallyInterruptive = false;
+
+    public RankingBuilder() {
+    }
+
+    public RankingBuilder(Ranking ranking) {
+        mKey = ranking.getKey();
+        mRank = ranking.getRank();
+        mMatchesInterruptionFilter = ranking.matchesInterruptionFilter();
+        mVisibilityOverride = ranking.getVisibilityOverride();
+        mSuppressedVisualEffects = ranking.getSuppressedVisualEffects();
+        mImportance = ranking.getImportance();
+        mExplanation = ranking.getImportanceExplanation();
+        mOverrideGroupKey = ranking.getOverrideGroupKey();
+        mChannel = ranking.getChannel();
+        mAdditionalPeople = copyList(ranking.getAdditionalPeople());
+        mSnoozeCriteria = copyList(ranking.getSnoozeCriteria());
+        mCanShowBadge = ranking.canShowBadge();
+        mUserSentiment = ranking.getUserSentiment();
+        mIsSuspended = ranking.isSuspended();
+        mLastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
+        mNoisy = ranking.isNoisy();
+        mSmartActions = copyList(ranking.getSmartActions());
+        mSmartReplies = copyList(ranking.getSmartReplies());
+        mCanBubble = ranking.canBubble();
+    }
+
+    public Ranking build() {
+        final Ranking ranking = new Ranking();
+        ranking.populate(
+                mKey,
+                mRank,
+                mMatchesInterruptionFilter,
+                mVisibilityOverride,
+                mSuppressedVisualEffects,
+                mImportance,
+                mExplanation,
+                mOverrideGroupKey,
+                mChannel,
+                mAdditionalPeople,
+                mSnoozeCriteria,
+                mCanShowBadge,
+                mUserSentiment,
+                mIsSuspended,
+                mLastAudiblyAlertedMs,
+                mNoisy,
+                mSmartActions,
+                mSmartReplies,
+                mCanBubble,
+                mIsVisuallyInterruptive);
+        return ranking;
+    }
+
+    public RankingBuilder setKey(String key) {
+        mKey = key;
+        return this;
+    }
+
+    public RankingBuilder setRank(int rank) {
+        mRank = rank;
+        return this;
+    }
+
+    public RankingBuilder setMatchesInterruptionFilter(boolean matchesInterruptionFilter) {
+        mMatchesInterruptionFilter = matchesInterruptionFilter;
+        return this;
+    }
+
+    public RankingBuilder setVisibilityOverride(int visibilityOverride) {
+        mVisibilityOverride = visibilityOverride;
+        return this;
+    }
+
+    public RankingBuilder setSuppressedVisualEffects(int suppressedVisualEffects) {
+        mSuppressedVisualEffects = suppressedVisualEffects;
+        return this;
+    }
+
+    public RankingBuilder setExplanation(CharSequence explanation) {
+        mExplanation = explanation;
+        return this;
+    }
+
+    public RankingBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mOverrideGroupKey = overrideGroupKey;
+        return this;
+    }
+
+    public RankingBuilder setAdditionalPeople(ArrayList<String> additionalPeople) {
+        mAdditionalPeople = additionalPeople;
+        return this;
+    }
+
+    public RankingBuilder setSnoozeCriteria(
+            ArrayList<SnoozeCriterion> snoozeCriteria) {
+        mSnoozeCriteria = snoozeCriteria;
+        return this;
+    }
+
+    public RankingBuilder setCanShowBadge(boolean canShowBadge) {
+        mCanShowBadge = canShowBadge;
+        return this;
+    }
+
+    public RankingBuilder setSuspended(boolean suspended) {
+        mIsSuspended = suspended;
+        return this;
+    }
+
+    public RankingBuilder setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
+        mLastAudiblyAlertedMs = lastAudiblyAlertedMs;
+        return this;
+    }
+
+    public RankingBuilder setNoisy(boolean noisy) {
+        mNoisy = noisy;
+        return this;
+    }
+
+    public RankingBuilder setCanBubble(boolean canBubble) {
+        mCanBubble = canBubble;
+        return this;
+    }
+
+    public RankingBuilder setImportance(@Importance int importance) {
+        mImportance = importance;
+        return this;
+    }
+
+    public RankingBuilder setUserSentiment(int userSentiment) {
+        mUserSentiment = userSentiment;
+        return this;
+    }
+
+    public RankingBuilder setChannel(NotificationChannel channel) {
+        mChannel = channel;
+        return this;
+    }
+
+    public RankingBuilder setSmartActions(@NonNull ArrayList<Notification.Action> smartActions) {
+        mSmartActions = smartActions;
+        return this;
+    }
+
+    public RankingBuilder setSmartActions(Notification.Action... smartActions) {
+        mSmartActions = new ArrayList<>(Arrays.asList(smartActions));
+        return this;
+    }
+
+    public RankingBuilder setSmartReplies(@NonNull ArrayList<CharSequence> smartReplies) {
+        mSmartReplies = smartReplies;
+        return this;
+    }
+
+    public RankingBuilder setSmartReplies(CharSequence... smartReplies) {
+        mSmartReplies = new ArrayList<>(Arrays.asList(smartReplies));
+        return this;
+    }
+
+    private static <E> ArrayList<E> copyList(List<E> list) {
+        if (list == null) {
+            return null;
+        } else {
+            return new ArrayList<>(list);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
new file mode 100644
index 0000000..9bc962c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import android.app.Notification;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Convenience builder for {@link StatusBarNotification} since its constructor is terrifying.
+ *
+ * Only for use in tests.
+ */
+public class SbnBuilder {
+    private String mPkg = "test_pkg";
+    private String mOpPkg;
+    private int mId;
+    private String mTag;
+    private int mUid;
+    private int mInitialPid;
+    private Notification mNotification = new Notification();
+    private UserHandle mUser = UserHandle.of(0);
+    private String mOverrideGroupKey;
+    private long mPostTime;
+
+    public SbnBuilder() {
+    }
+
+    public SbnBuilder(StatusBarNotification source) {
+        mPkg = source.getPackageName();
+        mOpPkg = source.getOpPkg();
+        mId = source.getId();
+        mTag = source.getTag();
+        mUid = source.getUid();
+        mInitialPid = source.getInitialPid();
+        mNotification = source.getNotification();
+        mUser = source.getUser();
+        mOverrideGroupKey = source.getOverrideGroupKey();
+        mPostTime = source.getPostTime();
+    }
+
+    public StatusBarNotification build() {
+        return new StatusBarNotification(
+                mPkg,
+                mOpPkg,
+                mId,
+                mTag,
+                mUid,
+                mInitialPid,
+                mNotification,
+                mUser,
+                mOverrideGroupKey,
+                mPostTime);
+    }
+
+    public SbnBuilder setPkg(String pkg) {
+        mPkg = pkg;
+        return this;
+    }
+
+    public SbnBuilder setOpPkg(String opPkg) {
+        mOpPkg = opPkg;
+        return this;
+    }
+
+    public SbnBuilder setId(int id) {
+        mId = id;
+        return this;
+    }
+
+    public SbnBuilder setTag(String tag) {
+        mTag = tag;
+        return this;
+    }
+
+    public SbnBuilder setUid(int uid) {
+        mUid = uid;
+        return this;
+    }
+
+    public SbnBuilder setInitialPid(int initialPid) {
+        mInitialPid = initialPid;
+        return this;
+    }
+
+    public SbnBuilder setNotification(Notification notification) {
+        mNotification = notification;
+        return this;
+    }
+
+    public SbnBuilder setUser(UserHandle user) {
+        mUser = user;
+        return this;
+    }
+
+    public SbnBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mOverrideGroupKey = overrideGroupKey;
+        return this;
+    }
+
+    public SbnBuilder setPostTime(long postTime) {
+        mPostTime = postTime;
+        return this;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 185723f..88fb3e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -97,7 +97,9 @@
 
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
                 0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
-        mEntry = new NotificationEntry(mSbn);
+        mEntry = new NotificationEntryBuilder()
+                .setSbn(mSbn)
+                .build();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index d804b6f..99dc895 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -26,22 +26,17 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import dagger.Lazy;
-
 
 @SmallTest
 @org.junit.runner.RunWith(AndroidTestingRunner.class)
@@ -49,19 +44,17 @@
 public class DynamicPrivacyControllerTest extends SysuiTestCase {
 
     private DynamicPrivacyController mDynamicPrivacyController;
-    private UnlockMethodCache mCache = mock(UnlockMethodCache.class);
     private NotificationLockscreenUserManager mLockScreenUserManager
             = mock(NotificationLockscreenUserManager.class);
     private DynamicPrivacyController.Listener mListener
             = mock(DynamicPrivacyController.Listener.class);
-    private KeyguardMonitor mKeyguardMonitor = mock(KeyguardMonitor.class);
+    private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
 
     @Before
     public void setUp() throws Exception {
-        when(mCache.canSkipBouncer()).thenReturn(false);
-        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         mDynamicPrivacyController = new DynamicPrivacyController(
-                mLockScreenUserManager, mKeyguardMonitor, mCache,
+                mLockScreenUserManager, mKeyguardStateController,
                 mock(StatusBarStateController.class));
         mDynamicPrivacyController.setStatusBarKeyguardViewManager(
                 mock(StatusBarKeyguardViewManager.class));
@@ -71,7 +64,7 @@
     @Test
     public void testDynamicFalseWhenCannotSkipBouncer() {
         enableDynamicPrivacy();
-        when(mCache.canSkipBouncer()).thenReturn(false);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
         Assert.assertFalse("can't skip bouncer but is dynamically unlocked",
                 mDynamicPrivacyController.isDynamicallyUnlocked());
     }
@@ -79,16 +72,16 @@
     @Test
     public void testDynamicTrueWhenCanSkipBouncer() {
         enableDynamicPrivacy();
-        when(mCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         Assert.assertTrue("Isn't dynamically unlocked even though we can skip bouncer",
                 mDynamicPrivacyController.isDynamicallyUnlocked());
     }
 
     @Test
     public void testNotifiedWhenEnabled() {
-        when(mCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         enableDynamicPrivacy();
-        mDynamicPrivacyController.onUnlockMethodStateChanged();
+        mDynamicPrivacyController.onUnlockedChanged();
         verify(mListener).onDynamicPrivacyChanged();
     }
 
@@ -99,10 +92,10 @@
 
     @Test
     public void testNotNotifiedWithoutNotifications() {
-        when(mCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         when(mLockScreenUserManager.shouldHideNotifications(anyInt())).thenReturn(
                 true);
-        mDynamicPrivacyController.onUnlockMethodStateChanged();
+        mDynamicPrivacyController.onUnlockedChanged();
         verifyNoMoreInteractions(mListener);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
 import androidx.palette.graphics.Palette;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
 
 import org.junit.After;
 import org.junit.Before;
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 2ca1b06..30e02e6 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
@@ -65,6 +65,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -182,7 +183,7 @@
                     0,
                     NotificationManager.IMPORTANCE_DEFAULT,
                     null, null,
-                    null, null, null, true, sentiment, false, -1, false, null, null, false);
+                    null, null, null, true, sentiment, false, -1, false, null, null, false, false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
     }
@@ -201,7 +202,7 @@
                     null, null,
                     null, null, null, true,
                     NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
-                    false, smartActions, null, false);
+                    false, smartActions, null, false, false);
             return true;
         }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
     }
@@ -237,9 +238,16 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
-                0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0);
-        mEntry = new NotificationEntry(mSbn);
+
+        mEntry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setNotification(n.build())
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+        mSbn = mEntry.sbn();
+
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
         mEntryManager = new TestableNotificationEntryManager(mContext);
@@ -292,7 +300,7 @@
 
         assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry);
         assertNotNull(entry.getRow());
-        assertEquals(mEntry.userSentiment,
+        assertEquals(mEntry.getUserSentiment(),
                 NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL);
     }
 
@@ -317,7 +325,7 @@
         verify(mEntryListener).onPostEntryUpdated(mEntry);
 
         assertNotNull(mEntry.getRow());
-        assertEquals(mEntry.userSentiment,
+        assertEquals(mEntry.getUserSentiment(),
                 NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
     }
 
@@ -403,8 +411,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
-        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
+        assertEquals(1, mEntry.getSmartActions().size());
+        assertEquals("action", mEntry.getSmartActions().get(0).title);
         verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
     }
 
@@ -419,7 +427,7 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(0, mEntry.systemGeneratedSmartActions.size());
+        assertNull(mEntry.getSmartActions());
     }
 
     @Test
@@ -433,8 +441,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
-        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
+        assertEquals(1, mEntry.getSmartActions().size());
+        assertEquals("action", mEntry.getSmartActions().get(0).title);
     }
 
     @Test
@@ -448,8 +456,8 @@
 
         mEntryManager.updateNotificationRanking(mRankingMap);
         verify(mRow, never()).setEntry(eq(mEntry));
-        assertEquals(1, mEntry.systemGeneratedSmartActions.size());
-        assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
+        assertEquals(1, mEntry.getSmartActions().size());
+        assertEquals("action", mEntry.getSmartActions().get(0).title);
     }
 
     @Test
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 6f7751b..edd0a10 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
@@ -41,6 +41,7 @@
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -195,13 +196,16 @@
 
         // test should filter out hidden notifications:
         // hidden
-        NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
-        entry.suspended = true;
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSuspended(true)
+                .build();
+
         assertTrue(mNotificationFilter.shouldFilterOut(entry));
 
         // not hidden
-        entry = new NotificationEntry(mMockStatusBarNotification);
-        entry.suspended = false;
+        entry = new NotificationEntryBuilder()
+                .setSuspended(false)
+                .build();
         assertFalse(mNotificationFilter.shouldFilterOut(entry));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 311b81c..8d496a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -30,7 +30,6 @@
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
@@ -41,6 +40,7 @@
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -73,7 +73,7 @@
     private DeviceProvisionedListener mProvisionedListener;
 
     // TODO: Remove this once EntryManager no longer needs to be mocked
-    private NotificationData mNotificationData = new NotificationData();
+    private NotificationData mNotificationData = new NotificationData(mContext);
 
     private int mNextNotifId = 0;
 
@@ -222,19 +222,14 @@
                 .setContentTitle("Title")
                 .setContentText("Text");
 
-        StatusBarNotification notification =
-                new StatusBarNotification(
-                        TEST_PACKAGE_NAME,
-                        TEST_PACKAGE_NAME,
-                        mNextNotifId,
-                        null,
-                        TEST_UID,
-                        0,
-                        n.build(),
-                        new UserHandle(ActivityManager.getCurrentUser()),
-                        null,
-                        0);
-        return new NotificationEntry(notification);
+        return new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setId(mNextNotifId)
+                .setUid(TEST_UID)
+                .setNotification(n.build())
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
     }
 
     private static final String TEST_PACKAGE_NAME = "test";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index dd2630b..6a4ddc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -25,13 +25,13 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
-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.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -62,7 +62,7 @@
 
         mVisualStabilityManager.setUpWithPresenter(mock(NotificationPresenter.class));
         mVisualStabilityManager.setVisibilityLocationProvider(mLocationProvider);
-        mEntry = new NotificationEntry(mock(StatusBarNotification.class));
+        mEntry = new NotificationEntryBuilder().build();
         mEntry.setRow(mRow);
 
         when(mRow.getEntry()).thenReturn(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index e2d8e56..657ec61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -27,10 +27,13 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_CHANNEL;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -47,6 +50,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -70,7 +74,10 @@
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -83,9 +90,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @SmallTest
@@ -98,8 +103,8 @@
     private static final NotificationChannel NOTIFICATION_CHANNEL =
             new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
 
-    private final StatusBarNotification mMockStatusBarNotification =
-            mock(StatusBarNotification.class);
+    private NotificationEntry mEntry;
+
     @Mock
     ForegroundServiceController mFsc;
     @Mock
@@ -113,9 +118,10 @@
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
-        when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
-        when(mMockStatusBarNotification.cloneLight()).thenReturn(mMockStatusBarNotification);
-        when(mMockStatusBarNotification.getKey()).thenReturn("mock_key");
+
+        mEntry = new NotificationEntryBuilder()
+                .setUid(UID_NORMAL)
+                .build();
 
         when(mMockPackageManager.checkUidPermission(
                 eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
@@ -133,7 +139,7 @@
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
         when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-        mNotificationData = new TestableNotificationData();
+        mNotificationData = new TestableNotificationData(mContext);
         mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class));
         mRow = new NotificationTestHelper(getContext()).createRow();
         Dependency.get(InitController.class).executePostInitTasks();
@@ -145,7 +151,7 @@
         override.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL);
         mNotificationData.rankingOverrides.put(mRow.getEntry().key, override);
         mNotificationData.add(mRow.getEntry());
-        assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel);
+        assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().getChannel());
     }
 
     @Test
@@ -231,57 +237,57 @@
     public void testIsExemptFromDndVisualSuppression_foreground() {
         initStatusBarNotification(false);
 
-        Notification n = mMockStatusBarNotification.getNotification();
-        n.flags = Notification.FLAG_FOREGROUND_SERVICE;
-        NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
-        entry.setRow(mRow);
-        mNotificationData.add(entry);
+        mEntry.sbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
+        mEntry.setRow(mRow);
+        mNotificationData.add(mEntry);
         Bundle override = new Bundle();
         override.putInt(OVERRIDE_VIS_EFFECTS, 255);
-        mNotificationData.rankingOverrides.put(entry.key, override);
+        mNotificationData.rankingOverrides.put(mEntry.key, override);
 
-        assertTrue(entry.isExemptFromDndVisualSuppression());
-        assertFalse(entry.shouldSuppressAmbient());
+        assertTrue(mEntry.isExemptFromDndVisualSuppression());
+        assertFalse(mEntry.shouldSuppressAmbient());
     }
 
     @Test
     public void testIsExemptFromDndVisualSuppression_media() {
         initStatusBarNotification(false);
-        Notification n = mMockStatusBarNotification.getNotification();
+        Notification n = mEntry.sbn().getNotification();
         Notification.Builder nb = Notification.Builder.recoverBuilder(mContext, n);
         nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
         n = nb.build();
-        when(mMockStatusBarNotification.getNotification()).thenReturn(n);
-        NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
-        entry.setRow(mRow);
-        mNotificationData.add(entry);
+        modifySbn(mEntry)
+                .setNotification(n)
+                .build();
+        mEntry.setRow(mRow);
+        mNotificationData.add(mEntry);
         Bundle override = new Bundle();
         override.putInt(OVERRIDE_VIS_EFFECTS, 255);
-        mNotificationData.rankingOverrides.put(entry.key, override);
+        mNotificationData.rankingOverrides.put(mEntry.key, override);
 
-        assertTrue(entry.isExemptFromDndVisualSuppression());
-        assertFalse(entry.shouldSuppressAmbient());
+        assertTrue(mEntry.isExemptFromDndVisualSuppression());
+        assertFalse(mEntry.shouldSuppressAmbient());
     }
 
     @Test
     public void testIsExemptFromDndVisualSuppression_system() {
         initStatusBarNotification(false);
-        NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
-        entry.setRow(mRow);
-        entry.mIsSystemNotification = true;
-        mNotificationData.add(entry);
+        mEntry.setRow(mRow);
+        mEntry.mIsSystemNotification = true;
+        mNotificationData.add(mEntry);
         Bundle override = new Bundle();
         override.putInt(OVERRIDE_VIS_EFFECTS, 255);
-        mNotificationData.rankingOverrides.put(entry.key, override);
+        mNotificationData.rankingOverrides.put(mEntry.key, override);
 
-        assertTrue(entry.isExemptFromDndVisualSuppression());
-        assertFalse(entry.shouldSuppressAmbient());
+        assertTrue(mEntry.isExemptFromDndVisualSuppression());
+        assertFalse(mEntry.shouldSuppressAmbient());
     }
 
     @Test
     public void testIsNotExemptFromDndVisualSuppression_hiddenCategories() {
         initStatusBarNotification(false);
-        NotificationEntry entry = new NotificationEntry(mMockStatusBarNotification);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setUid(UID_NORMAL)
+                .build();
         entry.setRow(mRow);
         entry.mIsSystemNotification = true;
         Bundle override = new Bundle();
@@ -289,63 +295,70 @@
         mNotificationData.rankingOverrides.put(entry.key, override);
         mNotificationData.add(entry);
 
-        when(mMockStatusBarNotification.getNotification()).thenReturn(
-                new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build());
-
+        modifySbn(entry)
+                .setNotification(
+                        new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build())
+                .build();
         assertFalse(entry.isExemptFromDndVisualSuppression());
         assertTrue(entry.shouldSuppressAmbient());
 
-        when(mMockStatusBarNotification.getNotification()).thenReturn(
-                new Notification.Builder(mContext, "").setCategory(CATEGORY_REMINDER).build());
-
+        modifySbn(entry)
+                .setNotification(
+                        new Notification.Builder(mContext, "")
+                                .setCategory(CATEGORY_REMINDER)
+                                .build())
+                .build();
         assertFalse(entry.isExemptFromDndVisualSuppression());
 
-        when(mMockStatusBarNotification.getNotification()).thenReturn(
-                new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build());
-
+        modifySbn(entry)
+                .setNotification(
+                        new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build())
+                .build();
         assertFalse(entry.isExemptFromDndVisualSuppression());
 
-        when(mMockStatusBarNotification.getNotification()).thenReturn(
-                new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build());
-
+        modifySbn(entry)
+                .setNotification(
+                        new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build())
+                .build();
         assertFalse(entry.isExemptFromDndVisualSuppression());
 
-        when(mMockStatusBarNotification.getNotification()).thenReturn(
-                new Notification.Builder(mContext, "").setCategory(CATEGORY_MESSAGE).build());
-
+        modifySbn(entry)
+                .setNotification(
+                        new Notification.Builder(mContext, "")
+                                .setCategory(CATEGORY_MESSAGE)
+                                .build())
+                .build();
         assertFalse(entry.isExemptFromDndVisualSuppression());
     }
 
     @Test
     public void testCreateNotificationDataEntry_RankingUpdate() {
-        Ranking ranking = mock(Ranking.class);
-        initStatusBarNotification(false);
+        StatusBarNotification sbn = new SbnBuilder().build();
+        sbn.getNotification().actions =
+                new Notification.Action[] { createContextualAction("appGeneratedAction") };
 
-        List<Notification.Action> appGeneratedSmartActions =
-                Collections.singletonList(createContextualAction("appGeneratedAction"));
-        mMockStatusBarNotification.getNotification().actions =
-                appGeneratedSmartActions.toArray(new Notification.Action[0]);
-
-        List<Notification.Action> systemGeneratedSmartActions =
-                Collections.singletonList(createAction("systemGeneratedAction"));
-        when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions);
-
-        when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
-
-        when(ranking.getUserSentiment()).thenReturn(Ranking.USER_SENTIMENT_NEGATIVE);
+        ArrayList<Notification.Action> systemGeneratedSmartActions =
+                createActions("systemGeneratedAction");
 
         SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
         ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
         snoozeCriterions.add(snoozeCriterion);
-        when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
+
+        Ranking ranking = new RankingBuilder()
+                .setKey(sbn.getKey())
+                .setSmartActions(systemGeneratedSmartActions)
+                .setChannel(NOTIFICATION_CHANNEL)
+                .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+                .setSnoozeCriteria(snoozeCriterions)
+                .build();
 
         NotificationEntry entry =
-                new NotificationEntry(mMockStatusBarNotification, ranking);
+                new NotificationEntry(sbn, ranking);
 
-        assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
-        assertEquals(NOTIFICATION_CHANNEL, entry.channel);
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
-        assertEquals(snoozeCriterions, entry.snoozeCriteria);
+        assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
+        assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
+        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment());
+        assertEquals(snoozeCriterions, entry.getSnoozeCriteria());
     }
 
     @Test
@@ -366,10 +379,15 @@
         Notification notification = new Notification.Builder(mContext, "test")
                 .addExtras(bundle)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                notification, mContext.getUser(), "", 0);
 
-        NotificationEntry entry = new NotificationEntry(sbn);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(notification)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
         entry.setHasSentReply();
 
         assertTrue(entry.isLastMessageFromReply());
@@ -472,9 +490,14 @@
         Notification aN = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.MessagingStyle(""))
                 .build();
-        StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                aN, mContext.getUser(), "", 0);
-        NotificationEntry a = new NotificationEntry(aSbn);
+        NotificationEntry a = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(aN)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
         a.setRow(mock(ExpandableNotificationRow.class));
         a.setIsHighPriority(false);
 
@@ -486,9 +509,14 @@
         Notification bN = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.MessagingStyle(""))
                 .build();
-        StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0,
-                bN, mContext.getUser(), "", 0);
-        NotificationEntry b = new NotificationEntry(bSbn);
+        NotificationEntry b = new NotificationEntryBuilder()
+                .setPkg("pkg2")
+                .setOpPkg("pkg2")
+                .setTag("tag")
+                .setNotification(bN)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
         b.setIsHighPriority(true);
         b.setRow(mock(ExpandableNotificationRow.class));
 
@@ -502,14 +530,18 @@
 
     @Test
     public void testSort_samePriorityUsesNMSRank() {
-        // NMS rank says A and then B. But A is not high priority and B is, so B should sort in
-        // front
+        // NMS rank says A and then B, and they are the same priority so use that rank
         Notification aN = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.MessagingStyle(""))
                 .build();
-        StatusBarNotification aSbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                aN, mContext.getUser(), "", 0);
-        NotificationEntry a = new NotificationEntry(aSbn);
+        NotificationEntry a = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(aN)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
         a.setRow(mock(ExpandableNotificationRow.class));
         a.setIsHighPriority(false);
 
@@ -521,9 +553,14 @@
         Notification bN = new Notification.Builder(mContext, "test")
                 .setStyle(new Notification.MessagingStyle(""))
                 .build();
-        StatusBarNotification bSbn = new StatusBarNotification("pkg2", "pkg2", 0, "tag", 0, 0,
-                bN, mContext.getUser(), "", 0);
-        NotificationEntry b = new NotificationEntry(bSbn);
+        NotificationEntry b = new NotificationEntryBuilder()
+                .setPkg("pkg2")
+                .setOpPkg("pkg2")
+                .setTag("tag")
+                .setNotification(bN)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
         b.setRow(mock(ExpandableNotificationRow.class));
         b.setIsHighPriority(false);
 
@@ -536,59 +573,50 @@
     }
 
     @Test
-    public void testSort_properlySetsIsTopBucket() {
-
+    public void testSort_properlySetsAlertingBucket() {
         Notification notification = new Notification.Builder(mContext, "test")
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification(
-                "pkg",
-                "pkg",
-                0,
-                "tag",
-                0,
-                0,
-                notification,
-                mContext.getUser(),
-                "",
-                0);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(notification)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
 
         Bundle override = new Bundle();
         override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_DEFAULT);
-        mNotificationData.rankingOverrides.put(sbn.getKey(), override);
+        mNotificationData.rankingOverrides.put(entry.key(), override);
 
-        NotificationEntry entry = new NotificationEntry(sbn);
         entry.setRow(mRow);
         mNotificationData.add(entry);
 
-        assertTrue(entry.isTopBucket());
+        assertEquals(entry.getBucket(), BUCKET_ALERTING);
     }
 
     @Test
-    public void testSort_properlySetsIsNotTopBucket() {
+    public void testSort_properlySetsSilentBucket() {
         Notification notification = new Notification.Builder(mContext, "test")
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification(
-                "pkg",
-                "pkg",
-                0,
-                "tag",
-                0,
-                0,
-                notification,
-                mContext.getUser(),
-                "",
-                0);
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("pkg")
+                .setOpPkg("pkg")
+                .setTag("tag")
+                .setNotification(notification)
+                .setUser(mContext.getUser())
+                .setOverrideGroupKey("")
+                .build();
 
         Bundle override = new Bundle();
         override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW);
-        mNotificationData.rankingOverrides.put(sbn.getKey(), override);
+        mNotificationData.rankingOverrides.put(entry.key(), override);
 
-        NotificationEntry entry = new NotificationEntry(sbn);
         entry.setRow(mRow);
-
         mNotificationData.add(entry);
 
-        assertFalse(entry.isTopBucket());
+        assertEquals(entry.getBucket(), BUCKET_SILENT);
     }
 
     private void initStatusBarNotification(boolean allowDuringSetup) {
@@ -597,12 +625,14 @@
         Notification notification = new Notification.Builder(mContext, "test")
                 .addExtras(bundle)
                 .build();
-        when(mMockStatusBarNotification.getNotification()).thenReturn(notification);
+        modifySbn(mEntry)
+                .setNotification(notification)
+                .build();
     }
 
     public static class TestableNotificationData extends NotificationData {
-        public TestableNotificationData() {
-            super();
+        public TestableNotificationData(Context context) {
+            super(context);
         }
 
         public static final String OVERRIDE_RANK = "r";
@@ -623,6 +653,7 @@
         public static final String OVERRIDE_SMART_ACTIONS = "sa";
         public static final String OVERRIDE_SMART_REPLIES = "sr";
         public static final String OVERRIDE_BUBBLE = "cb";
+        public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi";
 
         public Map<String, Bundle> rankingOverrides = new HashMap<>();
 
@@ -683,7 +714,14 @@
                         overrides.containsKey(OVERRIDE_SMART_REPLIES)
                                 ? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES)
                                 : currentReplies,
-                        overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()));
+                        overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()),
+                        overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE,
+                                outRanking.visuallyInterruptive()));
+            } else {
+                outRanking.populate(
+                        new RankingBuilder()
+                                .setKey(key)
+                                .build());
             }
             return true;
         }
@@ -704,4 +742,12 @@
                 title,
                 PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
     }
+
+    private ArrayList<Notification.Action> createActions(String... titles) {
+        ArrayList<Notification.Action> actions = new ArrayList<>();
+        for (String title : titles) {
+            actions.add(createAction(title));
+        }
+        return actions;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
deleted file mode 100644
index cca9f28..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ /dev/null
@@ -1,121 +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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationEntryTest extends SysuiTestCase {
-    @Mock
-    private StatusBarNotification mStatusBarNotification;
-    @Mock
-    private Notification mNotif;
-
-    private NotificationEntry mEntry;
-    private Bundle mExtras;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mStatusBarNotification.getKey()).thenReturn("key");
-        when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
-
-        mExtras = new Bundle();
-        mNotif.extras = mExtras;
-
-        mEntry = new NotificationEntry(mStatusBarNotification);
-    }
-
-    @Test
-    public void testGetUpdateMessage_default() {
-        final String msg = "Hello there!";
-        doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
-        mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
-    }
-
-    @Test
-    public void testGetUpdateMessage_bigText() {
-        final String msg = "A big hello there!";
-        doReturn(Notification.BigTextStyle.class).when(mNotif).getNotificationStyle();
-        mExtras.putCharSequence(Notification.EXTRA_TEXT, "A small hello there.");
-        mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
-
-        // Should be big text, not the small text.
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
-    }
-
-    @Test
-    public void testGetUpdateMessage_media() {
-        doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
-
-        // Media notifs don't get update messages.
-        assertNull(mEntry.getUpdateMessage(mContext));
-    }
-
-    @Test
-    public void testGetUpdateMessage_inboxStyle() {
-        doReturn(Notification.InboxStyle.class).when(mNotif).getNotificationStyle();
-        mExtras.putCharSequenceArray(
-                Notification.EXTRA_TEXT_LINES,
-                new CharSequence[]{
-                        "How do you feel about tests?",
-                        "They're okay, I guess.",
-                        "I hate when they're flaky.",
-                        "Really? I prefer them that way."});
-
-        // Should be the last one only.
-        assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
-    }
-
-    @Test
-    public void testGetUpdateMessage_messagingStyle() {
-        doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
-        mExtras.putParcelableArray(
-                Notification.EXTRA_MESSAGES,
-                new Bundle[]{
-                        new Notification.MessagingStyle.Message(
-                                "Hello", 0, "Josh").toBundle(),
-                        new Notification.MessagingStyle.Message(
-                                "Oh, hello!", 0, "Mady").toBundle()});
-
-        // Should be the last one only.
-        assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
-    }
-}
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 5e587f8..24cd056 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
@@ -29,7 +29,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -40,6 +39,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -93,10 +93,13 @@
 
         when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
 
-        StatusBarNotification sbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
-                0, null, TEST_UID,
-                0, new Notification(), UserHandle.CURRENT, null, 0);
-        mEntry = new NotificationEntry(sbn);
+        mEntry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setUid(TEST_UID)
+                .setNotification(new Notification())
+                .setUser(UserHandle.CURRENT)
+                .build();
         mEntry.setRow(mRow);
 
         mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index d526d10..c56a168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
@@ -327,8 +328,11 @@
         // Give each child a unique channel id/name.
         int i = 0;
         for (ExpandableNotificationRow childRow : childRows) {
-            childRow.getEntry().channel =
-                    new NotificationChannel("id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT);
+            modifyRanking(childRow.getEntry())
+                    .setChannel(
+                            new NotificationChannel(
+                                    "id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT))
+                    .build();
             i++;
         }
 
@@ -364,7 +368,7 @@
     public void testGetIsNonblockable_oemLocked() throws Exception {
         ExpandableNotificationRow row =
                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
-        row.getEntry().channel.setImportanceLockedByOEM(true);
+        row.getEntry().getChannel().setImportanceLockedByOEM(true);
 
         assertTrue(row.getIsNonblockable());
     }
@@ -373,7 +377,7 @@
     public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
         ExpandableNotificationRow row =
                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
-        row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true);
+        row.getEntry().getChannel().setImportanceLockedByCriticalDeviceFunction(true);
 
         assertTrue(row.getIsNonblockable());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 3c91b3f..6d64395 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -21,6 +21,8 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -41,6 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -73,6 +76,7 @@
     public void setUp() {
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(BubbleController.class);
         when(mGutsManager.openGuts(
                 any(View.class),
                 anyInt(),
@@ -82,6 +86,7 @@
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
         mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectMockDependency(BubbleController.class);
 
         mHelper = new NotificationTestHelper(mContext);
 
@@ -126,7 +131,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_shown() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
 
@@ -138,11 +145,16 @@
         ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
         int i = 0;
         for (ExpandableNotificationRow childRow : groupRow.getNotificationChildren()) {
-            childRow.getEntry().channel =
-                    new NotificationChannel(Integer.toString(i++), "", IMPORTANCE_DEFAULT);
+            modifyRanking(childRow.getEntry())
+                    .setChannel(
+                            new NotificationChannel(
+                                    Integer.toString(i++), "", IMPORTANCE_DEFAULT))
+                    .build();
         }
 
-        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(groupRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
 
@@ -152,7 +164,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception {
         ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
-        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(groupRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
 
@@ -167,7 +181,9 @@
         // as other factors such as view expansion may cause us to get the parent row back instead
         // of the child row.
         ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
-        childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(childRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         assertFalse(childRow.getIsNonblockable());
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
@@ -178,7 +194,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_notShownDueToNeutralUserSentiment() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEUTRAL;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEUTRAL)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -187,7 +205,9 @@
     public void testPerhapsShowBlockingHelper_notShownDueToPositiveUserSentiment()
             throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_POSITIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_POSITIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -195,7 +215,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_notShownDueToShadeVisibility() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         // Hide the shade
         mBlockingHelperManager.setNotificationShadeExpanded(0f);
 
@@ -206,7 +228,9 @@
     public void testPerhapsShowBlockingHelper_notShownDueToNonblockability() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
         when(row.getIsNonblockable()).thenReturn(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -219,7 +243,9 @@
         // as other factors such as view expansion may cause us to get the parent row back instead
         // of the child row.
         ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
-        childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(childRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
     }
@@ -227,7 +253,9 @@
     @Test
     public void testBlockingHelperShowAndDismiss() throws Exception{
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.isAttachedToWindow()).thenReturn(true);
 
         // Show check
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 2ec125e..ccadcc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -23,7 +23,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -44,12 +43,12 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 7959484..db6b613 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -22,6 +22,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -312,7 +314,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -341,7 +345,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(false);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -370,8 +376,10 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
-        row.getEntry().importance = IMPORTANCE_DEFAULT;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .setImportance(IMPORTANCE_DEFAULT)
+                .build();
         row.getEntry().setIsHighPriority(true);
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
@@ -401,7 +409,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(false);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
@@ -431,7 +441,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -498,7 +510,9 @@
 
         try {
             ExpandableNotificationRow row = mHelper.createRow(nb.build());
-            row.getEntry().channel = mTestNotificationChannel;
+            modifyRanking(row.getEntry())
+                    .setChannel(mTestNotificationChannel)
+                    .build();
             return row;
         } catch (Exception e) {
             fail();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 13b9d56..9eba4eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -28,9 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.NotificationChannel;
 import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -40,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -60,9 +59,7 @@
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mRow = mock(ExpandableNotificationRow.class);
-        NotificationEntry entry = new NotificationEntry(
-                mock(StatusBarNotification.class));
-        entry.channel = mock(NotificationChannel.class);
+        NotificationEntry entry = new NotificationEntryBuilder().build();
         when(mRow.getEntry()).thenReturn(entry);
     }
 
@@ -72,7 +69,6 @@
                 NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
     }
 
-
     @Test
     public void testAttachDetach() {
         NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 63e18ce..49a6410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 524ad85..addceb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -64,7 +64,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRoundnessManager = new NotificationRoundnessManager(mBypassController);
+        mRoundnessManager = new NotificationRoundnessManager(mBypassController, mContext);
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
         mFirst = testHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 59d0f91..56ed0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -74,7 +77,7 @@
                         mActivityStarterDelegate,
                         mStatusBarStateController,
                         mConfigurationController,
-                        true);
+                        2);
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
                 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
@@ -263,8 +266,8 @@
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
                     when(notifRow.getEntry().isHighPriority())
                             .thenReturn(children[i] == ChildType.HIPRI);
-                    when(notifRow.getEntry().isTopBucket())
-                            .thenReturn(children[i] == ChildType.HIPRI);
+                    when(notifRow.getEntry().getBucket()).thenReturn(
+                            children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
                     child = notifRow;
                     break;
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 fd67611..ff9aae7 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
@@ -39,6 +39,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -70,7 +71,7 @@
     @Mock
     private StatusBar mStatusBar;
     @Mock
-    private UnlockMethodCache mUnlockMethodCache;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private Handler mHandler;
     @Mock
@@ -82,7 +83,7 @@
         MockitoAnnotations.initMocks(this);
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
-        when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
@@ -90,7 +91,7 @@
         mDependency.injectTestDependency(StatusBarWindowController.class,
                 mStatusBarWindowController);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
-                mKeyguardViewMediator, mScrimController, mStatusBar, mUnlockMethodCache,
+                mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
                 mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 48934da..ef9665a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -32,6 +32,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -121,7 +122,9 @@
 
     @Test
     public void testCanRemoveImmediately_notTopEntry() {
-        NotificationEntry laterEntry = new NotificationEntry(createNewNotification(1));
+        NotificationEntry laterEntry = new NotificationEntryBuilder()
+                .setSbn(createNewNotification(1))
+                .build();
         laterEntry.setRow(mRow);
         mHeadsUpManager.showNotification(mEntry);
         mHeadsUpManager.showNotification(laterEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index cd60e47..3ba3e28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -83,7 +84,7 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
-    private UnlockMethodCache mUnlockMethodCache;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
@@ -95,13 +96,14 @@
     public void setup() {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
         DejankUtils.setImmediate(true);
         final ViewGroup container = new FrameLayout(getContext());
         when(mKeyguardHostView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardHostView.getHeight()).thenReturn(500);
         mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
                 mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager,
-                mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor,
+                mExpansionCallback, mKeyguardStateController, mKeyguardUpdateMonitor,
                 mKeyguardBypassController, mHandler) {
             @Override
             protected void inflateView() {
@@ -383,7 +385,7 @@
 
     @Test
     public void testShow_delaysIfFaceAuthIsRunning() {
-        when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         mBouncer.show(true /* reset */);
 
         ArgumentCaptor<Runnable> showRunnable = ArgumentCaptor.forClass(Runnable.class);
@@ -396,7 +398,7 @@
 
     @Test
     public void testShow_delaysIfFaceAuthIsRunning_unlessBypass() {
-        when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBouncer.show(true /* reset */);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 75aae01..33d3ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -82,8 +82,7 @@
     private FragmentController mControllerExternalDisplay;
 
     private SysuiTestableContext mSysuiTestableContextExternal;
-    private OverviewProxyService mOverviewProxyService =
-            mDependency.injectMockDependency(OverviewProxyService.class);
+    private OverviewProxyService mOverviewProxyService;
     private CommandQueue mCommandQueue;
     private SysUiState mMockSysUiState;
 
@@ -115,6 +114,8 @@
     public void setupFragment() throws Exception {
         setupSysuiDependency();
         createRootView();
+        mOverviewProxyService =
+                mDependency.injectMockDependency(OverviewProxyService.class);
         TestableLooper.get(this).runWithLooper(() -> {
             mHandler = new Handler();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
index cf08428..d6b38ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarRotationContextTest.java
@@ -30,7 +30,6 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
-import com.android.systemui.TestableDependency;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
@@ -50,7 +49,6 @@
     @Rule
     public final SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
-    private final TestableDependency mDependency = new TestableDependency(mContext);
     private RotationButtonController mRotationButtonController;
     private RotationButton mRotationButton;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index b1c3c83..a0d264d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -256,7 +256,7 @@
         StatusBarNotification oldNotification = childEntry.notification;
         StatusBarNotification newSbn = spy(childEntry.notification.clone());
         doReturn("other_group").when(newSbn).getGroupKey();
-        childEntry.notification = newSbn;
+        childEntry.setNotification(newSbn);
         mGroupManager.onEntryUpdated(childEntry, oldNotification);
 
         assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
@@ -267,7 +267,7 @@
         NotificationEntry summaryEntry =
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
-                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
+                mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47);
         when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
             .thenReturn(false);
         mHeadsUpManager.showNotification(summaryEntry);
@@ -277,8 +277,9 @@
 
         // Update that child to a summary.
         StatusBarNotification oldNotification = childEntry.notification;
-        childEntry.notification = mGroupTestHelper.createSummaryNotification(
-                Notification.GROUP_ALERT_SUMMARY).notification;
+        childEntry.setNotification(
+                mGroupTestHelper.createSummaryNotification(
+                        Notification.GROUP_ALERT_SUMMARY, 47).notification);
         mGroupManager.onEntryUpdated(childEntry, oldNotification);
 
         assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index e6b9778..f1a7905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -24,9 +24,9 @@
 import android.app.Notification;
 import android.content.Context;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -45,11 +45,15 @@
     }
 
     public NotificationEntry createSummaryNotification() {
-        return createSummaryNotification(Notification.GROUP_ALERT_ALL);
+        return createSummaryNotification(Notification.GROUP_ALERT_ALL, mId++);
     }
 
     public NotificationEntry createSummaryNotification(int groupAlertBehavior) {
-        return createEntry(true, groupAlertBehavior);
+        return createSummaryNotification(groupAlertBehavior, mId++);
+    }
+
+    public NotificationEntry createSummaryNotification(int groupAlertBehavior, int id) {
+        return createEntry(id, true, groupAlertBehavior);
     }
 
     public NotificationEntry createChildNotification() {
@@ -57,10 +61,14 @@
     }
 
     public NotificationEntry createChildNotification(int groupAlertBehavior) {
-        return createEntry(false, groupAlertBehavior);
+        return createEntry(mId++, false, groupAlertBehavior);
     }
 
-    public NotificationEntry createEntry(boolean isSummary, int groupAlertBehavior) {
+    public NotificationEntry createChildNotification(int groupAlertBehavior, int id) {
+        return createEntry(id, false, groupAlertBehavior);
+    }
+
+    public NotificationEntry createEntry(int id, boolean isSummary, int groupAlertBehavior) {
         Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentTitle("Title")
                 .setSmallIcon(R.drawable.ic_person)
@@ -68,22 +76,18 @@
                 .setGroupSummary(isSummary)
                 .setGroup(TEST_GROUP_ID)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                mId++,
-                null /* tag */,
-                0, /* uid */
-                0 /* initialPid */,
-                notif,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-        NotificationEntry entry = new NotificationEntry(sbn);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(TEST_PACKAGE_NAME)
+                .setOpPkg(TEST_PACKAGE_NAME)
+                .setId(id)
+                .setNotification(notif)
+                .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+                .build();
+
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         entry.setRow(row);
         when(row.getEntry()).thenReturn(entry);
-        when(row.getStatusBarNotification()).thenReturn(sbn);
+        when(row.getStatusBarNotification()).thenReturn(entry.sbn());
         when(row.isInflationFlagSet(anyInt())).thenReturn(true);
         return entry;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index a96efd7..219aef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -21,23 +21,28 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.StatusBarManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -50,7 +55,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
@@ -100,7 +104,11 @@
     @Mock
     private KeyguardAffordanceHelper mAffordanceHelper;
     @Mock
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock
     private FalsingManager mFalsingManager;
+    @Mock
+    private KeyguardBypassController mKeyguardBypassController;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -111,22 +119,21 @@
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mStatusBarStateController);
+        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectMockDependency(ConfigurationController.class);
         mDependency.injectMockDependency(ZenModeController.class);
-        KeyguardBypassController bypassController = new KeyguardBypassController(mContext,
-                mock(TunerService.class), mStatusBarStateController,
-                mock(NotificationLockscreenUserManager.class));
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(mContext,
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(),
-                        bypassController);
+                        mKeyguardBypassController);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
-                bypassController, mHeadsUpManager, mock(NotificationRoundnessManager.class));
+                mKeyguardBypassController, mHeadsUpManager,
+                mock(NotificationRoundnessManager.class), mStatusBarStateController);
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
-                bypassController);
+                mKeyguardBypassController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelView.setBar(mPanelBar);
 
@@ -186,6 +193,20 @@
         assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
     }
 
+    @Test
+    public void testKeyguardStatusBarVisibility_hiddenForBypass() {
+        when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
+        verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+        mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
+        verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+    }
+
     private class TestableNotificationPanelView extends NotificationPanelView {
         TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
                 PulseExpansionHandler expansionHandler,
@@ -195,7 +216,7 @@
                             SystemUIFactory.getInstance().getRootComponent()),
                     coordinator, expansionHandler, mock(DynamicPrivacyController.class),
                     bypassController,
-                    mFalsingManager);
+                    mFalsingManager, mock(PluginManager.class));
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
             mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
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 97ad47e..f1aaf3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -45,9 +45,10 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.ScrimView;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -69,6 +70,7 @@
     private SynchronousScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mScrimInFront;
+    private ScrimView mScrimForBubble;
     private ScrimState mScrimState;
     private float mScrimBehindAlpha;
     private GradientColors mScrimInFrontColor;
@@ -84,21 +86,24 @@
     public void setup() {
         mScrimBehind = spy(new ScrimView(getContext()));
         mScrimInFront = new ScrimView(getContext());
+        mScrimForBubble = new ScrimView(getContext());
         mWakeLock = mock(WakeLock.class);
         mAlarmManager = mock(AlarmManager.class);
         mAlwaysOnEnabled = true;
         mDozeParamenters = mock(DozeParameters.class);
         mLooper = TestableLooper.get(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
         when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
         mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
+                mScrimForBubble,
                 (scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
                     mScrimState = scrimState;
                     mScrimBehindAlpha = scrimBehindAlpha;
                     mScrimInFrontColor = scrimInFrontColor;
                 },
                 visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager,
-                mock(KeyguardMonitor.class));
+                mock(KeyguardStateController.class));
         mScrimController.setHasBackdrop(false);
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -114,21 +119,28 @@
     public void transitionToKeyguard() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
     public void transitionToAod_withRegularWallpaper() {
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -136,14 +148,18 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // Pulsing notification should conserve AOD wallpaper.
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -152,11 +168,14 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -166,11 +185,14 @@
         mScrimController.finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -179,27 +201,32 @@
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setAodFrontScrimAlpha(0.5f);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... but that it does take effect once we enter the AOD state.
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be semi-transparent
-        // Back scrim should be visible
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and that if we set it while we're in AOD, it does take immediate effect.
         mScrimController.setAodFrontScrimAlpha(1f);
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and make sure we recall the previous front scrim alpha even if we transition away
         // for a bit.
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and alpha updates should be completely ignored if always_on is off.
         // Passing it forward would mess up the wake-up transition.
@@ -216,27 +243,46 @@
     }
 
     @Test
-    public void transitionToPulsing() {
+    public void transitionToPulsing_withFrontAlphaUpdates() {
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
         // the back scrim opacity - otherwise it would hide AoD wallpapers.
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent, but tinted
         // Back scrim should be semi-transparent so the user can see the wallpaper
         // Pulse callback should have been invoked
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
+
+        // ... and when ambient goes dark, front scrim should be semi-transparent
+        mScrimController.setAodFrontScrimAlpha(0.5f);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be semi-transparent
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.setWakeLockScreenSensorActive(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        // Reset value since enums are static.
+        mScrimController.setAodFrontScrimAlpha(0f);
     }
 
     @Test
@@ -245,8 +291,13 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -255,8 +306,12 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -264,15 +319,19 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
-        assertScrimTint(mScrimInFront, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
 
         // Back scrim should be visible after start dragging
         mScrimController.setPanelExpansion(0.5f);
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -280,12 +339,19 @@
         mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
         mScrimController.finishAnimationsImmediately();
 
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
+
         // Front scrim should be transparent
-        Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+        Assert.assertEquals(ScrimController.TRANSPARENT,
                 mScrimInFront.getViewAlpha(), 0.0f);
         // Back scrim should be visible
         Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
                 mScrimBehind.getViewAlpha(), 0.0f);
+        // Bubble scrim should be visible
+        Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+                mScrimBehind.getViewAlpha(), 0.0f);
     }
 
     @Test
@@ -354,16 +420,22 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.finishAnimationsImmediately();
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        // Immediately tinted after the transition starts
-        assertScrimTint(mScrimInFront, true /* tinted */);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        // Immediately tinted black after the transition starts
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                true  /* bubble */);
+
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        // Neither scrims should be tinted anymore after the animation.
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimInFront, false /* tinted */);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+
+        // All scrims should be transparent at the end of fade transition.
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false  /* bubble */);
     }
 
     @Test
@@ -378,9 +450,11 @@
                         // Front scrim should be black in the middle of the transition
                         Assert.assertTrue("Scrim should be visible during transition. Alpha: "
                                 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
-                        assertScrimTint(mScrimInFront, true /* tinted */);
+                        assertScrimTint(true /* front */,
+                                true /* behind */,
+                                true /* bubble */);
                         Assert.assertSame("Scrim should be visible during transition.",
-                                mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
+                                mScrimVisibility, OPAQUE);
                     }
                 });
         mScrimController.finishAnimationsImmediately();
@@ -588,11 +662,15 @@
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -600,11 +678,15 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.setKeyguardOccluded(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -643,33 +725,68 @@
                 mScrimInFront.getDefaultFocusHighlightEnabled());
         Assert.assertFalse("Scrim shouldn't have focus highlight",
                 mScrimBehind.getDefaultFocusHighlightEnabled());
+        Assert.assertFalse("Scrim shouldn't have focus highlight",
+                mScrimForBubble.getDefaultFocusHighlightEnabled());
     }
 
-    private void assertScrimTint(ScrimView scrimView, boolean tinted) {
-        final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
-        final String name = scrimView == mScrimInFront ? "front" : "back";
+    private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
         Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
-                +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
-                tinted, viewIsTinted);
+                        + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
+                        + Integer.toHexString(mScrimInFront.getTint()),
+                front, mScrimInFront.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimBehind) + " and tint: "
+                        + Integer.toHexString(mScrimBehind.getTint()),
+                behind, mScrimBehind.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimForBubble) + " and tint: "
+                        + Integer.toHexString(mScrimForBubble.getTint()),
+                bubble, mScrimForBubble.getTint() != Color.TRANSPARENT);
     }
 
-    private void assertScrimVisibility(int inFront, int behind) {
-        boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
-                + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
-        Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
-                + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
-
-        final int visibility;
-        if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
-            visibility = VISIBILITY_FULLY_OPAQUE;
-        } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
-            visibility = VISIBILITY_SEMI_TRANSPARENT;
-        } else {
-            visibility = VISIBILITY_FULLY_TRANSPARENT;
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front";
+        } else if (scrim == mScrimBehind) {
+            return "back";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble";
         }
-        Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
+        return "unknown_scrim";
+    }
+
+    private void assertScrimAlpha(int front, int behind, int bubble) {
+        // Check single scrim visibility.
+        Assert.assertEquals("Unexpected front scrim alpha: "
+                        + mScrimInFront.getViewAlpha(),
+                front != TRANSPARENT /* expected */,
+                mScrimInFront.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals("Unexpected back scrim alpha: "
+                        + mScrimBehind.getViewAlpha(),
+                behind != TRANSPARENT /* expected */,
+                mScrimBehind.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals(
+                "Unexpected bubble scrim alpha: "
+                        + mScrimForBubble.getViewAlpha(), /* message */
+                bubble != TRANSPARENT /* expected */,
+                mScrimForBubble.getViewAlpha() > TRANSPARENT /* actual */);
+
+        // Check combined scrim visibility.
+        final int visibility;
+        if (front == OPAQUE || behind == OPAQUE || bubble == OPAQUE) {
+            visibility = OPAQUE;
+        } else if (front > TRANSPARENT || behind > TRANSPARENT || bubble > TRANSPARENT) {
+            visibility = SEMI_TRANSPARENT;
+        } else {
+            visibility = TRANSPARENT;
+        }
+        Assert.assertEquals("Invalid visibility.",
+                visibility /* expected */,
+                mScrimVisibility);
     }
 
     /**
@@ -681,11 +798,12 @@
         boolean mOnPreDrawCalled;
 
         SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+                ScrimView scrimForBubble,
                 TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
                 Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
-                AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
-            super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
-                    dozeParameters, alarmManager, keyguardMonitor);
+                AlarmManager alarmManager, KeyguardStateController keyguardStateController) {
+            super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+                    scrimVisibleListener, dozeParameters, alarmManager, keyguardStateController);
         }
 
         @Override
@@ -696,13 +814,14 @@
 
         void finishAnimationsImmediately() {
             boolean[] animationFinished = {false};
-            setOnAnimationFinished(()-> animationFinished[0] = true);
+            setOnAnimationFinished(() -> animationFinished[0] = true);
             // Execute code that will trigger animations.
             onPreDraw();
             // Force finish all animations.
             mLooper.processAllMessages();
             endAnimation(mScrimBehind, TAG_KEY_ANIM);
             endAnimation(mScrimInFront, TAG_KEY_ANIM);
+            endAnimation(mScrimForBubble, TAG_KEY_ANIM);
 
             if (!animationFinished[0]) {
                 throw new IllegalStateException("Animation never finished");
@@ -740,6 +859,7 @@
 
         /**
          * Do not wait for a frame since we're in a test environment.
+         *
          * @param callback What to execute.
          */
         @Override
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 63f653b..c3b25ce 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
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -45,6 +46,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +66,8 @@
     @Mock
     private KeyguardBouncer mBouncer;
     @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
     private StatusBar mStatusBar;
     @Mock
     private ViewGroup mContainer;
@@ -87,7 +91,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
+        mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
         when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
         when(mLockIconContainer.animate()).thenReturn(mock(ViewPropertyAnimator.class,
                 RETURNS_DEEP_STUBS));
@@ -167,7 +173,7 @@
 
     @Test
     public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
-        when(mStatusBar.isKeyguardCurrentlySecure()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(0.5f /* expansion */,
                 true /* tracking */);
         verify(mBouncer).show(eq(false), eq(false));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 06d76eb..266abcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -71,7 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -104,7 +104,7 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
-    private KeyguardMonitor mKeyguardMonitor;
+    private KeyguardStateController mKeyguardStateController;
     @Mock
     private Handler mHandler;
     @Mock
@@ -153,8 +153,6 @@
         StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
         bubbleSbn.getNotification().contentIntent = mContentIntent;
         bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
-        // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
-        mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
 
         mActiveNotifications = new ArrayList<>();
         mActiveNotifications.add(mNotificationRow.getEntry());
@@ -169,7 +167,8 @@
                 mock(StatusBarStateController.class), mock(KeyguardManager.class),
                 mock(IDreamManager.class), mRemoteInputManager,
                 mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
-                mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardMonitor,
+                mock(NotificationLockscreenUserManager.class), mShadeController,
+                mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
                 mBubbleController);
@@ -202,7 +201,7 @@
         sbn.getNotification().contentIntent = mContentIntent;
         sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
 
-        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mShadeController.isOccluded()).thenReturn(true);
 
         // When
@@ -265,7 +264,7 @@
 
         // Given
         sbn.getNotification().contentIntent = null;
-        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mShadeController.isOccluded()).thenReturn(true);
 
         // When
@@ -295,7 +294,7 @@
 
         // Given
         sbn.getNotification().contentIntent = mContentIntent;
-        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mShadeController.isOccluded()).thenReturn(true);
 
         // When
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 186a8c7..24ec109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -24,8 +24,6 @@
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.metrics.LogMaker;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,6 +37,7 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
@@ -84,29 +83,35 @@
     @Test
     public void testHeadsUp_disabledStatusBar() {
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
         mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
 
         assertFalse("The panel shouldn't allow heads up while disabled",
-                mStatusBar.canHeadsUp(entry, sbn));
+                mStatusBar.canHeadsUp(entry, entry.sbn()));
     }
 
     @Test
     public void testHeadsUp_disabledNotificationShade() {
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .build();
         mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE,
                 false /* animate */);
         TestableLooper.get(this).processAllMessages();
 
         assertFalse("The panel shouldn't allow heads up while notitifcation shade disabled",
-                mStatusBar.canHeadsUp(entry, sbn));
+                mStatusBar.canHeadsUp(entry, entry.sbn()));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index a97832f..4b6ca56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -68,7 +68,7 @@
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
                 mNotificationLockscreenUserManager);
-        mDependency.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
 
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                 mock(NotificationGroupManager.class)));
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 fa235bd..3be71c0 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
@@ -51,9 +51,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
-import android.service.notification.StatusBarNotification;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -88,6 +86,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -111,7 +110,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 
 import org.junit.Before;
@@ -132,7 +131,7 @@
 @RunWithLooper
 public class StatusBarTest extends SysuiTestCase {
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private UnlockMethodCache mUnlockMethodCache;
+    @Mock private KeyguardStateController mKeyguardStateController;
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
@@ -192,7 +191,6 @@
                 mViewHierarchyManager);
         mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
         mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
-        mDependency.injectTestDependency(KeyguardMonitor.class, mock(KeyguardMonitor.class));
         mDependency.injectTestDependency(AppOpsController.class, mock(AppOpsController.class));
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
         mDependency.injectTestDependency(DeviceProvisionedController.class,
@@ -208,7 +206,8 @@
 
         mNotificationInterruptionStateProvider =
                 new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
-                        mDreamManager, mAmbientDisplayConfiguration);
+                        mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
+                        mStatusBarStateController);
         mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
                 mNotificationInterruptionStateProvider);
         mDependency.injectMockDependency(NavigationBarController.class);
@@ -253,7 +252,7 @@
                 mHeadsUpManager, mHeadsUpSuppressor);
 
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
-        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
+        mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager,
                 mKeyguardIndicationController, mStackScroller,
                 mPowerManager, mNotificationPanelView, mBarService, mNotificationListener,
                 mNotificationLogger, mVisualStabilityManager, mViewHierarchyManager,
@@ -270,6 +269,7 @@
         SystemUIFactory.getInstance().getRootComponent()
                 .getStatusBarInjector()
                 .createStatusBar(mStatusBar);
+        mStatusBar.mKeyguardStateController = mKeyguardStateController;
         mStatusBar.setHeadsUpManager(mHeadsUpManager);
         mStatusBar.putComponent(StatusBar.class, mStatusBar);
         Dependency.get(InitController.class).executePostInitTasks();
@@ -313,11 +313,11 @@
     public void lockscreenStateMetrics_notShowing() {
         // uninteresting state, except that fingerprint must be non-zero
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
-        when(mUnlockMethodCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mUnlockMethodCache.isMethodSecure()).thenReturn(false);
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
         mStatusBar.onKeyguardViewManagerStatesUpdated();
 
         MetricsAsserts.assertHasLog("missing hidden insecure lockscreen log",
@@ -331,11 +331,11 @@
     public void lockscreenStateMetrics_notShowing_secure() {
         // uninteresting state, except that fingerprint must be non-zero
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
-        when(mUnlockMethodCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(false);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mUnlockMethodCache.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
         mStatusBar.onKeyguardViewManagerStatesUpdated();
 
@@ -350,11 +350,11 @@
     public void lockscreenStateMetrics_isShowing() {
         // uninteresting state, except that fingerprint must be non-zero
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
-        when(mUnlockMethodCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mUnlockMethodCache.isMethodSecure()).thenReturn(false);
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(false);
 
         mStatusBar.onKeyguardViewManagerStatesUpdated();
 
@@ -369,11 +369,11 @@
     public void lockscreenStateMetrics_isShowing_secure() {
         // uninteresting state, except that fingerprint must be non-zero
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
-        when(mUnlockMethodCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
-        when(mUnlockMethodCache.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
         mStatusBar.onKeyguardViewManagerStatesUpdated();
 
@@ -388,11 +388,11 @@
     public void lockscreenStateMetrics_isShowingBouncer() {
         // uninteresting state, except that fingerprint must be non-zero
         when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(false);
-        when(mUnlockMethodCache.canSkipBouncer()).thenReturn(true);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
         // interesting state
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mUnlockMethodCache.isMethodSecure()).thenReturn(true);
+        when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
 
         mStatusBar.onKeyguardViewManagerStatesUpdated();
 
@@ -416,10 +416,14 @@
                 .setGroupSummary(true)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
-        entry.importance = IMPORTANCE_HIGH;
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
 
         assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
@@ -437,10 +441,14 @@
                 .setGroupSummary(true)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
-        entry.importance = IMPORTANCE_HIGH;
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
 
         assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
@@ -454,11 +462,15 @@
         when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
-        entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
-        entry.importance = IMPORTANCE_HIGH;
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
+                .build();
 
         assertFalse(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
@@ -472,10 +484,14 @@
         when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = new NotificationEntry(sbn);
-        entry.importance = IMPORTANCE_HIGH;
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
 
         assertTrue(mNotificationInterruptionStateProvider.shouldHeadsUp(entry));
     }
@@ -760,7 +776,7 @@
 
     static class TestableStatusBar extends StatusBar {
         public TestableStatusBar(StatusBarKeyguardViewManager man,
-                UnlockMethodCache unlock, KeyguardIndicationController key,
+                KeyguardIndicationController key,
                 NotificationStackScrollLayout stack,
                 PowerManager pm, NotificationPanelView panelView,
                 IStatusBarService barService, NotificationListener notificationListener,
@@ -787,7 +803,6 @@
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
                 StatusBarWindowView statusBarWindow) {
             mStatusBarKeyguardViewManager = man;
-            mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
             mStackScroller = stack;
             mPowerManager = pm;
@@ -870,8 +885,11 @@
                 Context context,
                 PowerManager powerManager,
                 IDreamManager dreamManager,
-                AmbientDisplayConfiguration ambientDisplayConfiguration) {
-            super(context, powerManager, dreamManager, ambientDisplayConfiguration);
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter filter,
+                StatusBarStateController controller) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+                    controller);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 766ad97..e629a4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -27,7 +27,6 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -78,6 +77,7 @@
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
                 mTestableLooper.getLooper(),
+                mTestableLooper.getLooper(),
                 mMockBluetoothManager);
     }
 
@@ -109,18 +109,13 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
-
         // Trigger the state getting.
         assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
 
-        mTestableLooper.processMessages(1);
-        mainLooper.processAllMessages();
+        mTestableLooper.processAllMessages();
 
         assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
         verify(callback).onBluetoothDevicesChanged();
-        mainLooper.destroy();
     }
 
     @Test
@@ -130,20 +125,15 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
-
         // Trigger the state getting.
         assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 mBluetoothControllerImpl.getMaxConnectionState(device));
 
-        mTestableLooper.processMessages(1);
-        mainLooper.processAllMessages();
+        mTestableLooper.processAllMessages();
 
         assertEquals(BluetoothProfile.STATE_CONNECTED,
                 mBluetoothControllerImpl.getMaxConnectionState(device));
         verify(callback).onBluetoothDevicesChanged();
-        mainLooper.destroy();
     }
 
     @Test
@@ -153,19 +143,11 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+        // Trigger the state getting.
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(null));
 
-        try {
-            // Trigger the state getting.
-            assertEquals(BluetoothProfile.STATE_DISCONNECTED,
-                    mBluetoothControllerImpl.getMaxConnectionState(null));
-
-            mTestableLooper.processMessages(1);
-            mainLooper.processAllMessages();
-        } finally {
-            mainLooper.destroy();
-        }
+        mTestableLooper.processAllMessages();
     }
 
     @Test
@@ -217,15 +199,8 @@
         mBluetoothControllerImpl.onAclConnectionStateChanged(device,
                 BluetoothProfile.STATE_CONNECTED);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+        mTestableLooper.processAllMessages();
 
-        try {
-            mTestableLooper.processAllMessages();
-            mainLooper.processAllMessages();
-        } finally {
-            mainLooper.destroy();
-        }
         assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
         verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 2462fb3..c1f376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -15,6 +15,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -29,7 +31,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Icon;
-import android.service.notification.StatusBarNotification;
 import android.util.Pair;
 
 import androidx.test.annotation.UiThreadTest;
@@ -41,6 +42,8 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationEntryHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
 import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
@@ -65,7 +68,6 @@
             new Intent("com.android.WHITELISTED_TEST_ACTION");
 
     @Mock SmartReplyConstants mSmartReplyConstants;
-    @Mock StatusBarNotification mStatusBarNotification;
     @Mock Notification mNotification;
     NotificationEntry mEntry;
     @Mock RemoteInput mRemoteInput;
@@ -87,8 +89,9 @@
         mDependency.injectTestDependency(PackageManagerWrapper.class, mPackageManagerWrapper);
 
         when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true);
-        when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
-        mEntry = new NotificationEntry(mStatusBarNotification);
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotification)
+                .build();
         when(mSmartReplyConstants.isEnabled()).thenReturn(true);
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
 
@@ -99,7 +102,7 @@
     public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() {
         CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
         List<Notification.Action> smartActions =
-                createActions(new String[] {"Test Action 1", "Test Action 2"});
+                createActions("Test Action 1", "Test Action 2");
         setupAppGeneratedSuggestions(smartReplies, smartActions);
         when(mSmartReplyConstants.isEnabled()).thenReturn(false);
 
@@ -112,10 +115,11 @@
 
     @Test
     public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() {
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
+
         when(mSmartReplyConstants.isEnabled()).thenReturn(false);
 
         SmartRepliesAndActions repliesAndActions =
@@ -133,7 +137,7 @@
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
-        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies);
+        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
         assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
         assertThat(repliesAndActions.smartActions).isNull();
     }
@@ -142,13 +146,13 @@
     public void chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions() {
         CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
         List<Notification.Action> smartActions =
-                createActions(new String[] {"Test Action 1", "Test Action 2"});
+                createActions("Test Action 1", "Test Action 2");
         setupAppGeneratedSuggestions(smartReplies, smartActions);
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
-        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies);
+        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
         assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
         assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions);
         assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
@@ -160,13 +164,15 @@
         // replies.
         setupAppGeneratedReplies(null /* smartReplies */);
 
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .build();
+
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
         assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
-                mEntry.systemGeneratedSmartReplies);
+                mEntry.getSmartReplies());
         assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue();
         assertThat(repliesAndActions.smartActions).isNull();
     }
@@ -177,8 +183,9 @@
         // replies.
         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
 
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
+        NotificationEntryHelper.modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .build();
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
@@ -192,14 +199,15 @@
         // actions.
         setupAppGeneratedReplies(null /* smartReplies */);
 
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+        modifyRanking(mEntry)
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
         assertThat(repliesAndActions.smartReplies).isNull();
         assertThat(repliesAndActions.smartActions.actions)
-                .isEqualTo(mEntry.systemGeneratedSmartActions);
+                .isEqualTo(mEntry.getSmartActions());
         assertThat(repliesAndActions.smartActions.fromAssistant).isTrue();
     }
 
@@ -209,18 +217,19 @@
         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
         // replies.
         List<Notification.Action> appGenSmartActions =
-                createActions(new String[] {"Test Action 1", "Test Action 2"});
+                createActions("Test Action 1", "Test Action 2");
         setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions);
 
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
-        assertThat(repliesAndActions.smartReplies.choices).isEqualTo(appGenSmartReplies);
+        assertThat(repliesAndActions.smartReplies.choices)
+                .isEqualTo(Arrays.asList(appGenSmartReplies));
         assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
         assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions);
         assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
@@ -232,10 +241,11 @@
         // actions.
         setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */);
         when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false);
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
@@ -253,16 +263,17 @@
         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
         // suggestions.
         setupAppGeneratedReplies(null /* smartReplies */);
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
         assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
-                mEntry.systemGeneratedSmartReplies);
+                mEntry.getSmartReplies());
         // Since no apps are whitelisted no actions should be shown.
         assertThat(repliesAndActions.smartActions.actions).isEmpty();
     }
@@ -285,12 +296,14 @@
         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
         // suggestions.
         setupAppGeneratedReplies(null /* smartReplies */);
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        List<Notification.Action> actions = new ArrayList<>();
+        ArrayList<Notification.Action> actions = new ArrayList<>();
         actions.add(createAction("allowed action", WHITELISTED_TEST_INTENT));
         actions.add(createAction("non-allowed action", TEST_INTENT));
-        mEntry.systemGeneratedSmartActions = actions;
+
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(actions)
+                .build();
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
@@ -298,7 +311,7 @@
         // Only the action for the whitelisted package should be allowed.
         assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
         assertThat(repliesAndActions.smartActions.actions.get(0)).isEqualTo(
-                mEntry.systemGeneratedSmartActions.get(0));
+                mEntry.getSmartActions().get(0));
     }
 
     @Test
@@ -310,25 +323,25 @@
         // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
         // suggestions.
         setupAppGeneratedReplies(null /* smartReplies */);
-        mEntry.systemGeneratedSmartReplies =
-                new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"};
-        mEntry.systemGeneratedSmartActions =
-                createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"});
+        modifyRanking(mEntry)
+                .setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
+                .setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
+                .build();
 
         SmartRepliesAndActions repliesAndActions =
                 InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
 
         // We don't restrict replies or actions in screen pinning mode.
         assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
-                mEntry.systemGeneratedSmartReplies);
+                mEntry.getSmartReplies());
         assertThat(repliesAndActions.smartActions.actions).isEqualTo(
-                mEntry.systemGeneratedSmartActions);
+                mEntry.getSmartActions());
     }
 
     @Test
     public void areSuggestionsSimilar_trueForSimilar() {
-        CharSequence[] leftReplies = new CharSequence[] { "first reply", "second reply"};
-        CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+        List<CharSequence> leftReplies = createReplies("first reply", "second reply");
+        List<CharSequence> rightReplies = createReplies("first reply", "second reply");
         List<Notification.Action> leftActions = Arrays.asList(
                 createAction("firstAction"),
                 createAction("secondAction"));
@@ -349,8 +362,8 @@
 
     @Test
     public void areSuggestionsSimilar_falseForDifferentReplies() {
-        CharSequence[] leftReplies = new CharSequence[] { "first reply"};
-        CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+        List<CharSequence> leftReplies = createReplies("first reply");
+        List<CharSequence> rightReplies = createReplies("first reply", "second reply");
         List<Notification.Action> leftActions = Arrays.asList(
                 createAction("firstAction"),
                 createAction("secondAction"));
@@ -371,8 +384,8 @@
 
     @Test
     public void areSuggestionsSimilar_falseForDifferentActions() {
-        CharSequence[] leftReplies = new CharSequence[] { "first reply", "second reply"};
-        CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+        List<CharSequence> leftReplies = createReplies("first reply", "second reply");
+        List<CharSequence> rightReplies = createReplies("first reply", "second reply");
         List<Notification.Action> leftActions = Arrays.asList(
                 createAction("firstAction"),
                 createAction("secondAction"));
@@ -441,11 +454,15 @@
         return createActionBuilder(actionTitle, intent).build();
     }
 
-    private List<Notification.Action> createActions(String[] actionTitles) {
-        List<Notification.Action> actions = new ArrayList<>();
+    private ArrayList<Notification.Action> createActions(String... actionTitles) {
+        ArrayList<Notification.Action> actions = new ArrayList<>();
         for (String title : actionTitles) {
             actions.add(createAction(title));
         }
         return actions;
     }
+
+    private ArrayList<CharSequence> createReplies(CharSequence... replies) {
+        return new ArrayList<>(Arrays.asList(replies));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index e691b7d..c03f07e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.Instrumentation;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.Network;
@@ -48,6 +49,8 @@
 import android.testing.TestableResources;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.net.DataUsageController;
@@ -95,6 +98,7 @@
     protected SubscriptionDefaults mMockSubDefaults;
     protected DeviceProvisionedController mMockProvisionController;
     protected DeviceProvisionedListener mUserCallback;
+    protected Instrumentation mInstrumentation;
 
     protected int mSubId;
 
@@ -116,6 +120,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         Settings.Global.putInt(mContext.getContentResolver(), Global.AIRPLANE_MODE_ON, 0);
         TestableResources res = mContext.getOrCreateTestableResources();
         res.addOverride(R.string.cell_data_off_content_description, NO_DATA_STRING);
@@ -240,6 +245,16 @@
         NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_idle:5g", mConfig);
     }
 
+    public void setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds() {
+        final int enableDisplayGraceTimeSec = 30;
+        NetworkControllerImpl.Config.setDisplayGraceTime(enableDisplayGraceTimeSec, mConfig);
+    }
+
+    public void setupDefaultNr5GIconDisplayGracePeriodTime_disabled() {
+        final int disableDisplayGraceTimeSec = 0;
+        NetworkControllerImpl.Config.setDisplayGraceTime(disableDisplayGraceTimeSec, mConfig);
+    }
+
     public void setConnectivityViaBroadcast(
         int networkType, boolean validated, boolean isConnected) {
         setConnectivityCommon(networkType, validated, isConnected);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index f0394da..aa4723a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,7 @@
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -145,7 +147,7 @@
     }
 
     @Test
-    public void testNoInternetIcon_withoutDefaultSub() {
+    public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
@@ -156,11 +158,11 @@
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0,
                 true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false,
-                false, true, NOT_DEFAULT_DATA_STRING);
+                false, false, NOT_DEFAULT_DATA_STRING);
     }
 
     @Test
-    public void testDataDisabledIcon_withoutDefaultSub() {
+    public void testNonDefaultSIM_showsFullSignal_disconnected() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
@@ -171,7 +173,7 @@
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0,
                 true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false,
-                false, true, NOT_DEFAULT_DATA_STRING);
+                false, false, NOT_DEFAULT_DATA_STRING);
     }
 
     @Test
@@ -245,6 +247,186 @@
     }
 
     @Test
+    public void testNr5GIcon_displayGracePeriodTime_enabled() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        // While nrIconDisplayGracePeriodMs > 0 & is Nr5G, mIsShowingIconGracefully should be true
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs > 0);
+        assertTrue(mMobileSignalController.mIsShowingIconGracefully);
+    }
+
+    @Test
+    public void testNr5GIcon_displayGracePeriodTime_disabled() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        setupDefaultSignal();
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs == 0);
+
+        // While nrIconDisplayGracePeriodMs <= 0, mIsShowingIconGracefully should be false
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        assertFalse(mMobileSignalController.mIsShowingIconGracefully);
+    }
+
+    @Test
+    public void testNr5GIcon_enableDisplayGracePeriodTime_showIconGracefully() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Enabled timer Nr5G switch to None Nr5G, showing 5G icon gracefully
+        ServiceState ssLte = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(ssLte).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(ssLte).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ssLte);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+    }
+
+    @Test
+    public void testNr5GIcon_disableDisplayGracePeriodTime_showLatestIconImmediately() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_LTE);
+    }
+
+    @Test
+    public void testNr5GIcon_resetDisplayGracePeriodTime_whenDataDisconnected() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Disabled timer, when out of service, reset timer to display latest state
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_DISCONNECTED,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+
+        verifyDataIndicators(0);
+    }
+
+    @Test
+    public void testNr5GIcon_enableDisplayGracePeriodTime_show5G_switching_5GPlus() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss5G = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5G).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss5G).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5G);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // When timeout enabled, 5G/5G+ switching should be updated immediately
+        ServiceState ss5GPlus = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5GPlus).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(ss5GPlus).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5GPlus);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G_PLUS);
+    }
+
+    @Test
+    public void testNr5GIcon_carrierDisabledDisplayGracePeriodTime_shouldUpdateIconImmediately() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss5G = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5G).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss5G).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5G);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // State from NR_5G to NONE NR_5G with timeout, should show previous 5G icon
+        ServiceState ssLte = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(ssLte).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(ssLte).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ssLte);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Update nrIconDisplayGracePeriodMs to 0
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        mNetworkController.handleConfigurationChanged();
+
+        // State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs == 0);
+        verifyDataIndicators(TelephonyIcons.ICON_LTE);
+    }
+
+    @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 6e3d906..3451183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,5 +1,12 @@
 package com.android.systemui.statusbar.policy;
 
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.content.Intent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -16,13 +23,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0cb5754..0d56cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -23,7 +23,6 @@
 import static junit.framework.Assert.fail;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,7 +36,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -48,10 +46,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -65,6 +65,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
@@ -112,6 +113,7 @@
         mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
             action.onDismiss();
         });
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
@@ -137,10 +139,10 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text").build();
-        StatusBarNotification sbn = mock(StatusBarNotification.class);
-        when(sbn.getNotification()).thenReturn(mNotification);
-        when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY);
-        mEntry = new NotificationEntry(sbn);
+
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotification)
+                .build();
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
     }
@@ -468,7 +470,11 @@
                 new Intent(TEST_ACTION), 0);
         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
         SmartReplyView.SmartReplies smartReplies =
-                new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant);
+                new SmartReplyView.SmartReplies(
+                        Arrays.asList(choices),
+                        input,
+                        pendingIntent,
+                        fromAssistant);
         return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
                 useDelayedOnClickListener);
     }
@@ -494,6 +500,7 @@
     private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
         mView.resetSmartSuggestions(mContainer);
         List<Button> actions = mView.inflateSmartActions(
+                getContext(),
                 new SmartReplyView.SmartActions(createActions(actionTitles), false),
                 mLogger,
                 mEntry,
@@ -514,6 +521,7 @@
         List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
                 useDelayedOnClickListener);
         smartSuggestions.addAll(mView.inflateSmartActions(
+                getContext(),
                 new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
                 mLogger,
                 mEntry,
@@ -553,7 +561,7 @@
         // Add smart replies
         Button previous = null;
         SmartReplyView.SmartReplies smartReplies =
-                new SmartReplyView.SmartReplies(choices, null, null, false);
+                new SmartReplyView.SmartReplies(Arrays.asList(choices), null, null, false);
         for (int i = 0; i < choices.length; ++i) {
             Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
                     null /* SmartReplyController */, null /* NotificationEntry */,
@@ -860,7 +868,7 @@
     }
 
     private Button inflateActionButton(Notification.Action action) {
-        return SmartReplyView.inflateActionButton(mView, getContext(), 0,
+        return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0,
                 new SmartReplyView.SmartActions(Collections.singletonList(action), false),
                 mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
deleted file mode 100644
index 4a9b1b3..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
+++ /dev/null
@@ -1,110 +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.util;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.utils.hardware.FakeSensorManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class AsyncSensorManagerTest extends SysuiTestCase {
-
-    private TestableAsyncSensorManager mAsyncSensorManager;
-    private FakeSensorManager mFakeSensorManager;
-    private SensorEventListener mListener;
-    private FakeSensorManager.MockProximitySensor mSensor;
-    private PluginManager mPluginManager;
-
-    @Before
-    public void setUp() throws Exception {
-        mPluginManager = mock(PluginManager.class);
-        mFakeSensorManager = new FakeSensorManager(mContext);
-        mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager);
-        mSensor = mFakeSensorManager.getMockProximitySensor();
-        mListener = mock(SensorEventListener.class);
-    }
-
-    @Test
-    public void registerListenerImpl() throws Exception {
-        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
-
-        mAsyncSensorManager.waitUntilRequestsCompleted();
-
-        // Verify listener was registered.
-        mSensor.sendProximityResult(true);
-        verify(mListener).onSensorChanged(any());
-    }
-
-    @Test
-    public void unregisterListenerImpl_withNullSensor() throws Exception {
-        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
-        mAsyncSensorManager.unregisterListener(mListener);
-
-        mAsyncSensorManager.waitUntilRequestsCompleted();
-
-        // Verify listener was unregistered.
-        mSensor.sendProximityResult(true);
-        verifyNoMoreInteractions(mListener);
-    }
-
-    @Test
-    public void unregisterListenerImpl_withSensor() throws Exception {
-        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
-        mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
-
-        mAsyncSensorManager.waitUntilRequestsCompleted();
-
-        // Verify listener was unregistered.
-        mSensor.sendProximityResult(true);
-        verifyNoMoreInteractions(mListener);
-    }
-
-    @Test
-    public void registersPlugin_whenLoaded() {
-        verify(mPluginManager).addPluginListener(eq(mAsyncSensorManager),
-                eq(SensorManagerPlugin.class), eq(true) /* allowMultiple */);
-    }
-
-    private class TestableAsyncSensorManager extends AsyncSensorManager {
-        public TestableAsyncSensorManager(SensorManager sensorManager) {
-            super(sensorManager, mPluginManager);
-        }
-
-        public void waitUntilRequestsCompleted() {
-            assertTrue(mHandler.runWithScissors(() -> {}, 0));
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
new file mode 100644
index 0000000..426aba0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -0,0 +1,151 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
+import android.provider.DeviceConfig.Properties;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * A Fake of {@link DeviceConfigProxy} useful for testing.
+ *
+ * No properties are set by default. No calls to {@link DeviceConfig} are made. Be sure to set any
+ * properties you rely on ahead of time in your test.
+ */
+public class DeviceConfigProxyFake extends DeviceConfigProxy {
+
+    private List<Pair<Executor, OnPropertiesChangedListener>> mListeners = new ArrayList<>();
+    private Map<String, Map<String, String>> mDefaultProperties = new HashMap<>();
+    private Map<String, Map<String, String>> mProperties = new HashMap<>();
+
+    public DeviceConfigProxyFake() {
+    }
+
+    @Override
+    public void addOnPropertiesChangedListener(
+            String namespace, Executor executor,
+            OnPropertiesChangedListener onPropertiesChangedListener) {
+        mListeners.add(Pair.create(executor, onPropertiesChangedListener));
+    }
+
+    @Override
+    public void removeOnPropertiesChangedListener(
+            OnPropertiesChangedListener onPropertiesChangedListener) {
+        mListeners.removeIf(listener -> {
+            if (listener == null) {
+                return false;
+            }
+            return listener.second.equals(onPropertiesChangedListener);
+        });
+    }
+
+    @Override
+    public boolean setProperty(String namespace, String name, String value, boolean makeDefault) {
+        setPropertyInternal(namespace, name, value, mProperties);
+        if (makeDefault) {
+            setPropertyInternal(namespace, name, value, mDefaultProperties);
+        }
+
+        for (Pair<Executor, OnPropertiesChangedListener> listener : mListeners) {
+            listener.first.execute(() -> listener.second.onPropertiesChanged(
+                    new Properties(namespace, mProperties.get(namespace))));
+        }
+        return true;
+    }
+
+    private void setPropertyInternal(String namespace, String name, String value,
+            Map<String, Map<String, String>> properties) {
+        properties.putIfAbsent(namespace, new HashMap<>());
+        properties.get(namespace).put(name, value);
+    }
+
+    @Override
+    public void enforceReadPermission(Context context, String namespace) {
+        // no-op
+    }
+
+    private Properties propsForNamespaceAndName(String namespace, String name) {
+        if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
+            return new Properties(namespace, mProperties.get(namespace));
+        }
+        if (mDefaultProperties.containsKey(namespace)) {
+            return new Properties(namespace, mDefaultProperties.get(namespace));
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean getBoolean(String namespace, String name, boolean defaultValue) {
+        Properties props = propsForNamespaceAndName(namespace, name);
+        if (props != null) {
+            return props.getBoolean(name, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public int getInt(String namespace, String name, int defaultValue) {
+        Properties props = propsForNamespaceAndName(namespace, name);
+        if (props != null) {
+            return props.getInt(name, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public long getLong(String namespace, String name, long defaultValue) {
+        Properties props = propsForNamespaceAndName(namespace, name);
+        if (props != null) {
+            return props.getLong(name, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public String getProperty(String namespace, String name) {
+        return getString(namespace, name, null);
+    }
+
+    @Override
+    public String getString(String namespace, String name, String defaultValue) {
+        Properties props = propsForNamespaceAndName(namespace, name);
+        if (props != null) {
+            return props.getString(name, defaultValue);
+        }
+
+        return defaultValue;
+    }
+
+    @Override
+    public void resetToDefaults(int resetMode, String namespace) {
+        if (mProperties.containsKey(namespace)) {
+            mProperties.get(namespace).clear();
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
new file mode 100644
index 0000000..9149599
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.util.sensors;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.hardware.SensorEventListener;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AsyncSensorManagerTest extends SysuiTestCase {
+
+    private AsyncSensorManager mAsyncSensorManager;
+    private SensorEventListener mListener;
+    private FakeSensorManager.FakeProximitySensor mSensor;
+    private PluginManager mPluginManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mPluginManager = mock(PluginManager.class);
+        FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext);
+        mAsyncSensorManager = new AsyncSensorManager(
+                fakeSensorManager, mPluginManager, new Handler());
+        mSensor = fakeSensorManager.getFakeProximitySensor();
+        mListener = mock(SensorEventListener.class);
+    }
+
+    @Test
+    public void registerListenerImpl() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+
+        waitUntilRequestsCompleted();
+
+        // Verify listener was registered.
+        mSensor.sendProximityResult(true);
+        verify(mListener).onSensorChanged(any());
+    }
+
+    @Test
+    public void unregisterListenerImpl_withNullSensor() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+        mAsyncSensorManager.unregisterListener(mListener);
+
+        waitUntilRequestsCompleted();
+
+        // Verify listener was unregistered.
+        mSensor.sendProximityResult(true);
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void unregisterListenerImpl_withSensor() throws Exception {
+        mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+        mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
+
+        waitUntilRequestsCompleted();
+
+        // Verify listener was unregistered.
+        mSensor.sendProximityResult(true);
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
+    public void registersPlugin_whenLoaded() {
+        verify(mPluginManager).addPluginListener(eq(mAsyncSensorManager),
+                eq(SensorManagerPlugin.class), eq(true) /* allowMultiple */);
+    }
+
+    public void waitUntilRequestsCompleted() {
+        TestableLooper.get(this).processAllMessages();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
new file mode 100644
index 0000000..d7df96d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.content.Context;
+
+public class FakeProximitySensor extends ProximitySensor {
+    private boolean mAvailable;
+    private boolean mPaused;
+
+    public FakeProximitySensor(Context context, AsyncSensorManager sensorManager) {
+        super(context, sensorManager);
+
+        mAvailable = true;
+    }
+
+    public void setSensorAvailable(boolean available) {
+        mAvailable = available;
+    }
+
+    public void setLastEvent(ProximityEvent event) {
+        mLastEvent = event;
+    }
+
+    @Override
+    public boolean getSensorAvailable() {
+        return mAvailable;
+    }
+
+    @Override
+    protected void registerInternal() {
+        // no-op
+    }
+
+    @Override
+    protected void unregisterInternal() {
+        // no-op
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
new file mode 100644
index 0000000..1deb495
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -0,0 +1,283 @@
+/*
+ * 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.util.sensors;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEventListener;
+import android.os.Handler;
+import android.os.MemoryFile;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * Rudimentary fake for SensorManager
+ *
+ * Currently only supports proximity, light and tap sensors.
+ *
+ * Note that this class ignores the "Handler" argument, so the test is responsible for calling the
+ * listener on the right thread.
+ */
+public class FakeSensorManager extends SensorManager {
+
+    public static final String TAP_SENSOR_TYPE = "tapSensorType";
+
+    private final FakeProximitySensor mFakeProximitySensor;
+    private final FakeGenericSensor mFakeLightSensor;
+    private final FakeGenericSensor mFakeTapSensor;
+    private final FakeGenericSensor[] mSensors;
+
+    public FakeSensorManager(Context context) throws Exception {
+        Sensor proxSensor = context.getSystemService(SensorManager.class)
+                .getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (proxSensor == null) {
+            // No prox? Let's create a fake one!
+            proxSensor = createSensor(Sensor.TYPE_PROXIMITY, null);
+        }
+
+        mSensors = new FakeGenericSensor[]{
+                mFakeProximitySensor = new FakeProximitySensor(proxSensor),
+                mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
+                mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
+        };
+    }
+
+    public FakeProximitySensor getFakeProximitySensor() {
+        return mFakeProximitySensor;
+    }
+
+    public FakeGenericSensor getFakeLightSensor() {
+        return mFakeLightSensor;
+    }
+
+    public FakeGenericSensor getFakeTapSensor() {
+        return mFakeTapSensor;
+    }
+
+    @Override
+    public Sensor getDefaultSensor(int type) {
+        Sensor s = super.getDefaultSensor(type);
+        if (s != null) {
+            return s;
+        }
+        // Our mock sensors aren't wakeup, and it's a pain to create them that way. Instead, just
+        // return non-wakeup sensors if we can't find a wakeup sensor.
+        return getDefaultSensor(type, false /* wakeup */);
+    }
+
+    @Override
+    protected List<Sensor> getFullSensorList() {
+        return Arrays
+                .stream(mSensors)
+                .map(i -> i.mSensor)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    protected List<Sensor> getFullDynamicSensorList() {
+        return new ArrayList<>();
+    }
+
+    @Override
+    protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+        Preconditions.checkNotNull(listener);
+        for (FakeGenericSensor s : mSensors) {
+            if (sensor == null || s.mSensor == sensor) {
+                s.mListeners.remove(listener);
+            }
+        }
+    }
+
+    @Override
+    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
+            int delayUs,
+            Handler handler, int maxReportLatencyUs, int reservedFlags) {
+        Preconditions.checkNotNull(sensor);
+        Preconditions.checkNotNull(listener);
+        for (FakeGenericSensor s : mSensors) {
+            if (s.mSensor == sensor) {
+                s.mListeners.add(listener);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean flushImpl(SensorEventListener listener) {
+        return false;
+    }
+
+    @Override
+    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
+            HardwareBuffer hardwareBuffer) {
+        return null;
+    }
+
+    @Override
+    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+
+    }
+
+    @Override
+    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
+        return 0;
+    }
+
+    @Override
+    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
+            Handler handler) {
+
+    }
+
+    @Override
+    protected void unregisterDynamicSensorCallbackImpl(
+            DynamicSensorCallback callback) {
+
+    }
+
+    @Override
+    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        return true;
+    }
+
+    @Override
+    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+            boolean disable) {
+        return true;
+    }
+
+    @Override
+    protected boolean initDataInjectionImpl(boolean enable) {
+        return false;
+    }
+
+    @Override
+    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
+            long timestamp) {
+        return false;
+    }
+
+    @Override
+    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+        return false;
+    }
+
+    private Sensor createSensor(int type, @Nullable String stringType) throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+
+        setSensorType(sensor, type);
+        if (stringType != null) {
+            setSensorField(sensor, "mStringType", stringType);
+        }
+        setSensorField(sensor, "mName", "Mock " + sensor.getStringType() + "/" + type);
+        setSensorField(sensor, "mVendor", "Mock Vendor");
+        setSensorField(sensor, "mVersion", 1);
+        setSensorField(sensor, "mHandle", -1);
+        setSensorField(sensor, "mMaxRange", 10);
+        setSensorField(sensor, "mResolution", 1);
+        setSensorField(sensor, "mPower", 1);
+        setSensorField(sensor, "mMinDelay", 1000);
+        setSensorField(sensor, "mMaxDelay", 1000000000);
+        setSensorField(sensor, "mFlags", 0);
+        setSensorField(sensor, "mId", -1);
+
+        return sensor;
+    }
+
+    private void setSensorField(Sensor sensor, String fieldName, Object value) throws Exception {
+        Field field = Sensor.class.getDeclaredField(fieldName);
+        field.setAccessible(true);
+        field.set(sensor, value);
+    }
+
+    private void setSensorType(Sensor sensor, int type) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, type);
+    }
+
+    public class FakeProximitySensor extends FakeGenericSensor {
+
+        private FakeProximitySensor(Sensor sensor) {
+            super(sensor);
+        }
+
+        public void sendProximityResult(boolean far) {
+            sendSensorEvent(far ? getSensor().getMaximumRange() : 0);
+        }
+    }
+
+    public class FakeGenericSensor {
+
+        private final Sensor mSensor;
+        private final ArraySet<SensorEventListener> mListeners = new ArraySet<>();
+
+        public FakeGenericSensor(
+                Sensor sensor) {
+            this.mSensor = sensor;
+        }
+
+        public Sensor getSensor() {
+            return mSensor;
+        }
+
+        public void sendSensorEvent(float... values) {
+            SensorEvent event = createSensorEvent(values.length);
+            System.arraycopy(values, 0, event.values, 0, values.length);
+            for (SensorEventListener listener : mListeners) {
+                listener.onSensorChanged(event);
+            }
+        }
+
+        private SensorEvent createSensorEvent(int valuesSize) {
+            SensorEvent event;
+            try {
+                Constructor<SensorEvent> constr =
+                        SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
+                constr.setAccessible(true);
+                event = constr.newInstance(valuesSize);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            event.sensor = mSensor;
+            event.timestamp = SystemClock.elapsedRealtimeNanos();
+
+            return event;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
new file mode 100644
index 0000000..fa943e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.util.sensors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProximitySensorTest extends SysuiTestCase {
+
+    private ProximitySensor mProximitySensor;
+    private FakeSensorManager.FakeProximitySensor mFakeProximitySensor;
+
+    @Before
+    public void setUp() throws Exception {
+        FakeSensorManager sensorManager = new FakeSensorManager(getContext());
+        AsyncSensorManager asyncSensorManager = new AsyncSensorManager(
+                sensorManager, null, new Handler());
+        mFakeProximitySensor = sensorManager.getFakeProximitySensor();
+        mProximitySensor = new ProximitySensor(getContext(), asyncSensorManager);
+    }
+
+    @Test
+    public void testSingleListener() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 2);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+    }
+
+    @Test
+    public void testMultiListener() {
+        TestableListener listenerA = new TestableListener();
+        TestableListener listenerB = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+
+        mProximitySensor.register(listenerA);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        mProximitySensor.register(listenerB);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listenerA.mLastEvent);
+        assertNull(listenerB.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listenerA.mLastEvent.getNear());
+        assertFalse(listenerB.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 1);
+        assertEquals(listenerB.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listenerA.mLastEvent.getNear());
+        assertTrue(listenerB.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 2);
+        assertEquals(listenerB.mCallCount, 2);
+
+        mProximitySensor.unregister(listenerA);
+        mProximitySensor.unregister(listenerB);
+        waitForSensorManager();
+    }
+
+    @Test
+    public void testUnregister() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+    }
+
+    @Test
+    public void testPauseAndResume() {
+        TestableListener listener = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+        mProximitySensor.register(listener);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listener.mLastEvent);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.pause();
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+
+        // More events do nothing when paused.
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+        mFakeProximitySensor.sendProximityResult(false);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mProximitySensor.resume();
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        // Still matches our previous call
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 1);
+
+        mFakeProximitySensor.sendProximityResult(true);
+        assertFalse(listener.mLastEvent.getNear());
+        assertEquals(listener.mCallCount, 2);
+
+        mProximitySensor.unregister(listener);
+        waitForSensorManager();
+        assertFalse(mProximitySensor.isRegistered());
+    }
+
+    @Test
+    public void testAlertListeners() {
+        TestableListener listenerA = new TestableListener();
+        TestableListener listenerB = new TestableListener();
+
+        assertFalse(mProximitySensor.isRegistered());
+
+        mProximitySensor.register(listenerA);
+        mProximitySensor.register(listenerB);
+        waitForSensorManager();
+        assertTrue(mProximitySensor.isRegistered());
+        assertNull(listenerA.mLastEvent);
+        assertNull(listenerB.mLastEvent);
+
+        mProximitySensor.alertListeners();
+        assertNull(listenerA.mLastEvent);
+        assertEquals(listenerA.mCallCount, 1);
+        assertNull(listenerB.mLastEvent);
+        assertEquals(listenerB.mCallCount, 1);
+
+        mFakeProximitySensor.sendProximityResult(false);
+        assertTrue(listenerA.mLastEvent.getNear());
+        assertEquals(listenerA.mCallCount, 2);
+        assertTrue(listenerB.mLastEvent.getNear());
+        assertEquals(listenerB.mCallCount, 2);
+
+        mProximitySensor.unregister(listenerA);
+        mProximitySensor.unregister(listenerB);
+        waitForSensorManager();
+    }
+
+    class TestableListener implements ProximitySensor.ProximitySensorListener {
+        ProximitySensor.ProximityEvent mLastEvent;
+        int mCallCount = 0;
+
+        @Override
+        public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
+            mLastEvent = proximityEvent;
+            mCallCount++;
+        }
+
+        void reset() {
+            mLastEvent = null;
+            mCallCount = 0;
+        }
+    };
+
+    private void waitForSensorManager() {
+        TestableLooper.get(this).processAllMessages();
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
deleted file mode 100644
index 29b8ab60..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
+++ /dev/null
@@ -1,283 +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.utils.hardware;
-
-import android.content.Context;
-import android.hardware.HardwareBuffer;
-import android.hardware.Sensor;
-import android.hardware.SensorAdditionalInfo;
-import android.hardware.SensorDirectChannel;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.TriggerEventListener;
-import android.os.Handler;
-import android.os.MemoryFile;
-import android.os.SystemClock;
-import android.util.ArraySet;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.annotation.Nullable;
-
-/**
- * Rudimentary fake for SensorManager
- *
- * Currently only supports proximity, light and tap sensors.
- *
- * Note that this class ignores the "Handler" argument, so the test is responsible for calling the
- * listener on the right thread.
- */
-public class FakeSensorManager extends SensorManager {
-
-    public static final String TAP_SENSOR_TYPE = "tapSensorType";
-
-    private final MockProximitySensor mMockProximitySensor;
-    private final FakeGenericSensor mFakeLightSensor;
-    private final FakeGenericSensor mFakeTapSensor;
-    private final FakeGenericSensor[] mSensors;
-
-    public FakeSensorManager(Context context) throws Exception {
-        Sensor proxSensor = context.getSystemService(SensorManager.class)
-                .getDefaultSensor(Sensor.TYPE_PROXIMITY);
-        if (proxSensor == null) {
-            // No prox? Let's create a fake one!
-            proxSensor = createSensor(Sensor.TYPE_PROXIMITY, null);
-        }
-
-        mSensors = new FakeGenericSensor[]{
-                mMockProximitySensor = new MockProximitySensor(proxSensor),
-                mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
-                mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
-        };
-    }
-
-    public MockProximitySensor getMockProximitySensor() {
-        return mMockProximitySensor;
-    }
-
-    public FakeGenericSensor getFakeLightSensor() {
-        return mFakeLightSensor;
-    }
-
-    public FakeGenericSensor getFakeTapSensor() {
-        return mFakeTapSensor;
-    }
-
-    @Override
-    public Sensor getDefaultSensor(int type) {
-        Sensor s = super.getDefaultSensor(type);
-        if (s != null) {
-            return s;
-        }
-        // Our mock sensors aren't wakeup, and it's a pain to create them that way. Instead, just
-        // return non-wakeup sensors if we can't find a wakeup sensor.
-        return getDefaultSensor(type, false /* wakeup */);
-    }
-
-    @Override
-    protected List<Sensor> getFullSensorList() {
-        return Arrays
-                .stream(mSensors)
-                .map(i -> i.mSensor)
-                .collect(Collectors.toList());
-    }
-
-    @Override
-    protected List<Sensor> getFullDynamicSensorList() {
-        return new ArrayList<>();
-    }
-
-    @Override
-    protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
-        Preconditions.checkNotNull(listener);
-        for (FakeGenericSensor s : mSensors) {
-            if (sensor == null || s.mSensor == sensor) {
-                s.mListeners.remove(listener);
-            }
-        }
-    }
-
-    @Override
-    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
-            int delayUs,
-            Handler handler, int maxReportLatencyUs, int reservedFlags) {
-        Preconditions.checkNotNull(sensor);
-        Preconditions.checkNotNull(listener);
-        for (FakeGenericSensor s : mSensors) {
-            if (s.mSensor == sensor) {
-                s.mListeners.add(listener);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean flushImpl(SensorEventListener listener) {
-        return false;
-    }
-
-    @Override
-    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
-            HardwareBuffer hardwareBuffer) {
-        return null;
-    }
-
-    @Override
-    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
-
-    }
-
-    @Override
-    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
-        return 0;
-    }
-
-    @Override
-    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
-            Handler handler) {
-
-    }
-
-    @Override
-    protected void unregisterDynamicSensorCallbackImpl(
-            DynamicSensorCallback callback) {
-
-    }
-
-    @Override
-    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
-        return true;
-    }
-
-    @Override
-    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
-            boolean disable) {
-        return true;
-    }
-
-    @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
-        return false;
-    }
-
-    @Override
-    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
-            long timestamp) {
-        return false;
-    }
-
-    @Override
-    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
-        return false;
-    }
-
-    private Sensor createSensor(int type, @Nullable String stringType) throws Exception {
-        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
-        constr.setAccessible(true);
-        Sensor sensor = constr.newInstance();
-
-        setSensorType(sensor, type);
-        if (stringType != null) {
-            setSensorField(sensor, "mStringType", stringType);
-        }
-        setSensorField(sensor, "mName", "Mock " + sensor.getStringType() + "/" + type);
-        setSensorField(sensor, "mVendor", "Mock Vendor");
-        setSensorField(sensor, "mVersion", 1);
-        setSensorField(sensor, "mHandle", -1);
-        setSensorField(sensor, "mMaxRange", 10);
-        setSensorField(sensor, "mResolution", 1);
-        setSensorField(sensor, "mPower", 1);
-        setSensorField(sensor, "mMinDelay", 1000);
-        setSensorField(sensor, "mMaxDelay", 1000000000);
-        setSensorField(sensor, "mFlags", 0);
-        setSensorField(sensor, "mId", -1);
-
-        return sensor;
-    }
-
-    private void setSensorField(Sensor sensor, String fieldName, Object value) throws Exception {
-        Field field = Sensor.class.getDeclaredField(fieldName);
-        field.setAccessible(true);
-        field.set(sensor, value);
-    }
-
-    private void setSensorType(Sensor sensor, int type) throws Exception {
-        Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
-        setter.setAccessible(true);
-        setter.invoke(sensor, type);
-    }
-
-    public class MockProximitySensor extends FakeGenericSensor {
-
-        private MockProximitySensor(Sensor sensor) {
-            super(sensor);
-        }
-
-        public void sendProximityResult(boolean far) {
-            sendSensorEvent(far ? getSensor().getMaximumRange() : 0);
-        }
-    }
-
-    public class FakeGenericSensor {
-
-        private final Sensor mSensor;
-        private final ArraySet<SensorEventListener> mListeners = new ArraySet<>();
-
-        public FakeGenericSensor(
-                Sensor sensor) {
-            this.mSensor = sensor;
-        }
-
-        public Sensor getSensor() {
-            return mSensor;
-        }
-
-        public void sendSensorEvent(float... values) {
-            SensorEvent event = createSensorEvent(values.length);
-            System.arraycopy(values, 0, event.values, 0, values.length);
-            for (SensorEventListener listener : mListeners) {
-                listener.onSensorChanged(event);
-            }
-        }
-
-        private SensorEvent createSensorEvent(int valuesSize) {
-            SensorEvent event;
-            try {
-                Constructor<SensorEvent> constr =
-                        SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
-                constr.setAccessible(true);
-                event = constr.newInstance(valuesSize);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-            event.sensor = mSensor;
-            event.timestamp = SystemClock.elapsedRealtimeNanos();
-
-            return event;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
deleted file mode 100644
index 95c7a4d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
+++ /dev/null
@@ -1,88 +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.utils.leaks;
-
-import android.testing.LeakCheck;
-
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-
-public class FakeKeyguardMonitor implements KeyguardMonitor {
-
-    private final BaseLeakChecker<Callback> mCallbackController;
-
-    public FakeKeyguardMonitor(LeakCheck test) {
-        mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
-    }
-
-    @Override
-    public void addCallback(Callback callback) {
-        mCallbackController.addCallback(callback);
-    }
-
-    @Override
-    public void removeCallback(Callback callback) {
-        mCallbackController.removeCallback(callback);
-    }
-
-    @Override
-    public boolean isSecure() {
-        return false;
-    }
-
-    @Override
-    public boolean isShowing() {
-        return false;
-    }
-
-    @Override
-    public boolean isOccluded() {
-        return false;
-    }
-
-    @Override
-    public boolean isKeyguardFadingAway() {
-        return false;
-    }
-
-    @Override
-    public boolean isKeyguardGoingAway() {
-        return false;
-    }
-
-    @Override
-    public boolean isLaunchTransitionFadingAway() {
-        return false;
-    }
-
-    @Override
-    public long getKeyguardFadingAwayDuration() {
-        return 0;
-    }
-
-    @Override
-    public long getKeyguardFadingAwayDelay() {
-        return 0;
-    }
-
-    @Override
-    public long calculateGoingToFullShadeDelay() {
-        return 0;
-    }
-
-    @Override
-    public boolean canSkipBouncer() {
-        return false;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
new file mode 100644
index 0000000..26cac29
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -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.utils.leaks;
+
+import android.testing.LeakCheck;
+
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+public class FakeKeyguardStateController implements KeyguardStateController {
+
+    private final BaseLeakChecker<Callback> mCallbackController;
+
+    public FakeKeyguardStateController(LeakCheck test) {
+        mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        mCallbackController.addCallback(callback);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        mCallbackController.removeCallback(callback);
+    }
+
+    @Override
+    public boolean isMethodSecure() {
+        return false;
+    }
+
+    @Override
+    public boolean isShowing() {
+        return false;
+    }
+
+    @Override
+    public boolean canDismissLockScreen() {
+        return false;
+    }
+
+    @Override
+    public boolean isOccluded() {
+        return false;
+    }
+
+    @Override
+    public boolean isTrusted() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardFadingAway() {
+        return false;
+    }
+
+    @Override
+    public boolean isKeyguardGoingAway() {
+        return false;
+    }
+
+    @Override
+    public boolean isLaunchTransitionFadingAway() {
+        return false;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDuration() {
+        return 0;
+    }
+
+    @Override
+    public long getKeyguardFadingAwayDelay() {
+        return 0;
+    }
+
+    @Override
+    public long calculateGoingToFullShadeDelay() {
+        return 0;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index f479126..fedc08d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.utils.leaks;
 
-import static org.mockito.Matchers.any;
-
 import android.testing.LeakCheck;
 import android.util.ArrayMap;
 
@@ -29,7 +27,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.FlashlightController;
 import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -60,7 +58,7 @@
             HotspotController.class,
             FlashlightController.class,
             UserInfoController.class,
-            KeyguardMonitor.class,
+            KeyguardStateController.class,
             BatteryController.class,
             SecurityController.class,
             ManagedProfileController.class,
@@ -118,8 +116,8 @@
                     obj = new FakeFlashlightController(this);
                 } else if (cls == UserInfoController.class) {
                     obj = new FakeUserInfoController(this);
-                } else if (cls == KeyguardMonitor.class) {
-                    obj = new FakeKeyguardMonitor(this);
+                } else if (cls == KeyguardStateController.class) {
+                    obj = new FakeKeyguardStateController(this);
                 } else if (cls == BatteryController.class) {
                     obj = new FakeBatteryController(this);
                 } else if (cls == SecurityController.class) {
diff --git a/packages/WAPPushManager/Android.bp b/packages/WAPPushManager/Android.bp
index 1bec492..c391369 100644
--- a/packages/WAPPushManager/Android.bp
+++ b/packages/WAPPushManager/Android.bp
@@ -9,4 +9,6 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
+
+    product_specific: true,
 }
diff --git a/packages/WAPPushManager/CleanSpec.mk b/packages/WAPPushManager/CleanSpec.mk
index b84e1b6..2dcbb10 100644
--- a/packages/WAPPushManager/CleanSpec.mk
+++ b/packages/WAPPushManager/CleanSpec.mk
@@ -47,3 +47,5 @@
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/WAPPushManager)
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
index dc77981..0ff85fe 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"Сделать вырез в углу"</string>
+    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"В правом углу"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
index a02eaf7..2493da3 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Увеличить вырез вдвое"</string>
+    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Сверху и снизу"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
index 1d1656d..89ac1c3 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сделать вырез выше"</string>
+    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сверху"</string>
 </resources>
diff --git a/proto/src/gnss.proto b/proto/src/gnss.proto
index 1509fc0..33dbb26 100644
--- a/proto/src/gnss.proto
+++ b/proto/src/gnss.proto
@@ -48,6 +48,27 @@
 
   // Hardware revision (EVT, DVT, PVT etc.)
   optional string hardware_revision = 13;
+
+  // Total number of sv status messages processed
+  optional int32 num_sv_status_processed = 14;
+
+  // Total number of L5 sv status messages processed
+  optional int32 num_l5_sv_status_processed = 15;
+
+  // Total number of sv status messages processed, where sv is used in fix
+  optional int32 num_sv_status_used_in_fix = 16;
+
+  // Total number of L5 sv status messages processed, where sv is used in fix
+  optional int32 num_l5_sv_status_used_in_fix = 17;
+
+  // Number of l5 top 4 average CN0 processed
+  optional int32 num_l5_top_four_average_cn0_processed = 18;
+
+  // Mean of l5 top 4 average CN0 (dB-Hz)
+  optional double mean_l5_top_four_average_cn0_db_hz = 19;
+
+  // Standard deviation of l5 top 4 average CN0 (dB-Hz)
+  optional double standard_deviation_l5_top_four_average_cn0_db_hz = 20;
 }
 
 // Power metrics
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 5a4892c..5b826b1 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7414,6 +7414,17 @@
     // Note: Gear icon is shown next to gesture navigation preference and opens sensitivity dialog
     SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG = 1748;
 
+
+
+    // ACTION: Chooser > User taps a system-provided target such as copy
+    //   SUBTYPE: Index of target
+    // CATEGORY: GLOBAL_SYSTEM_UI
+    // OS: Q - QPR1
+    ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET = 1749;
+
+    // OPEN: Settings > System > Aware > Aware Display
+    // OS: Q
+    SETTINGS_AWARE_DISPLAY = 1750;
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 8ad2489..fbf6ca5 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1792,6 +1792,25 @@
 
   // Indicates if we are logging LinkSpeedCount in metrics
   optional bool link_speed_counts_logging_enabled = 4;
+
+  // Duration for evaluating Wifi condition to trigger a data stall
+  // measured in milliseconds
+  optional int32 data_stall_duration_ms = 5;
+
+  // Threshold of Tx throughput below which to trigger a data stall
+  // measured in Kbps
+  optional int32 data_stall_tx_tput_thr_kbps = 6;
+
+  // Threshold of Rx throughput below which to trigger a data stall
+  // measured in Kbps
+  optional int32 data_stall_rx_tput_thr_kbps = 7;
+
+  // Threshold of Tx packet error rate above which to trigger a data stall
+  // in percentage
+  optional int32 data_stall_tx_per_thr = 8;
+
+  // Threshold of CCA level above which to trigger a data stall in percentage
+  optional int32 data_stall_cca_level_thr = 9;
 }
 
 message WifiIsUnusableEvent {
diff --git a/services/Android.bp b/services/Android.bp
index 27f8d36..60dd895 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -47,6 +47,11 @@
         "compat-changeid-annotation-processor",
     ],
 
+    required: [
+      // Required by services.backup
+      "BackupEncryption",
+    ],
+
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
 
@@ -63,6 +68,5 @@
 
 platform_compat_config {
     name: "services-platform-compat-config",
-    prefix: "services",
     src: ":services",
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 1d936f2..4f021ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -23,7 +23,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
@@ -352,7 +352,7 @@
 
     @Override
     public List<AccessibilityWindowInfo> getWindows() {
-        ensureWindowsAvailableTimed();
+        ensureWindowsAvailableTimed(Display.DEFAULT_DISPLAY);
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -362,8 +362,6 @@
             if (!permissionGranted) {
                 return null;
             }
-            // TODO [Multi-Display] (b/134891479) :
-            // using correct display Id to replace DEFAULT_DISPLAY.
             List<AccessibilityWindowInfo> internalWindowList =
                     mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
             if (internalWindowList == null) {
@@ -387,7 +385,14 @@
 
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
-        ensureWindowsAvailableTimed();
+        int displayId = Display.INVALID_DISPLAY;
+        synchronized (mLock) {
+            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
+                        mSystemSupport.getCurrentUserIdLocked(), windowId);
+            }
+        }
+        ensureWindowsAvailableTimed(displayId);
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -1168,9 +1173,9 @@
         }
     }
 
-    public void notifyGesture(AccessibilityGestureInfo gestureInfo) {
+    public void notifyGesture(AccessibilityGestureEvent gestureEvent) {
         mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
-                gestureInfo).sendToTarget();
+                gestureEvent).sendToTarget();
     }
 
     public void notifyClearAccessibilityNodeInfoCache() {
@@ -1259,7 +1264,7 @@
         }
     }
 
-    private void notifyGestureInternal(AccessibilityGestureInfo gestureInfo) {
+    private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
@@ -1308,28 +1313,28 @@
 
     /**
      * Request that the system make sure windows are available to interrogate.
+     *
+     * @param displayId The logical display id.
      */
-    private void ensureWindowsAvailableTimed() {
+    private void ensureWindowsAvailableTimed(int displayId) {
         synchronized (mLock) {
-            // TODO [Multi-Display] (b/134891479) :
-            // using correct display Id to replace DEFAULT_DISPLAY.
-            if (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) != null) {
+            if (mA11yWindowManager.getWindowListLocked(displayId) != null) {
                 return;
             }
             // If we have no registered callback, update the state we
             // we may have to register one but it didn't happen yet.
-            if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+            if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                 // Invokes client change to make sure tracking window enabled.
                 mSystemSupport.onClientChangeLocked(false);
             }
             // We have no windows but do not care about them, done.
-            if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+            if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                 return;
             }
 
             // Wait for the windows with a timeout.
             final long startMillis = SystemClock.uptimeMillis();
-            while (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) == null) {
+            while (mA11yWindowManager.getWindowListLocked(displayId) == null) {
                 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
                 final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis;
                 if (remainMillis <= 0) {
@@ -1464,7 +1469,7 @@
             final int type = message.what;
             switch (type) {
                 case MSG_ON_GESTURE: {
-                    notifyGestureInternal((AccessibilityGestureInfo) message.obj);
+                    notifyGestureInternal((AccessibilityGestureEvent) message.obj);
                 } break;
 
                 case MSG_CLEAR_ACCESSIBILITY_CACHE: {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ddf5bbe..1f11059 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -27,7 +27,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -580,17 +580,24 @@
             // Make sure clients receiving this event will be able to get the
             // current state of the windows as the window manager may be delaying
             // the computation for performance reasons.
-            // TODO [Multi-Display] : using correct display Id to replace DEFAULT_DISPLAY.
             boolean shouldComputeWindows = false;
+            int displayId = Display.INVALID_DISPLAY;
             synchronized (mLock) {
+                final int windowId = event.getWindowId();
                 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-                        && mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+                        && windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                    displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
+                            mCurrentUserId, windowId);
+                }
+                if (displayId != Display.INVALID_DISPLAY
+                        && mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                     shouldComputeWindows = true;
                 }
             }
             if (shouldComputeWindows) {
-                WindowManagerInternal wm = LocalServices.getService(WindowManagerInternal.class);
-                wm.computeWindowsForAccessibility(Display.DEFAULT_DISPLAY);
+                final WindowManagerInternal wm = LocalServices.getService(
+                        WindowManagerInternal.class);
+                wm.computeWindowsForAccessibility(displayId);
             }
             synchronized (mLock) {
                 notifyAccessibilityServicesDelayedLocked(event, false);
@@ -821,12 +828,17 @@
         }
     }
 
-
-    public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+    /**
+     * Called when a gesture is detected on a display.
+     *
+     * @param gestureEvent the detail of the gesture.
+     * @return true if the event is handled.
+     */
+    public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
         synchronized (mLock) {
-            boolean handled = notifyGestureLocked(gestureInfo, false);
+            boolean handled = notifyGestureLocked(gestureEvent, false);
             if (!handled) {
-                handled = notifyGestureLocked(gestureInfo, true);
+                handled = notifyGestureLocked(gestureEvent, true);
             }
             return handled;
         }
@@ -1021,7 +1033,7 @@
         }
     }
 
-    private boolean notifyGestureLocked(AccessibilityGestureInfo gestureInfo, boolean isDefault) {
+    private boolean notifyGestureLocked(AccessibilityGestureEvent gestureEvent, boolean isDefault) {
         // TODO: Now we are giving the gestures to the last enabled
         //       service that can handle them which is the last one
         //       in our list since we write the last enabled as the
@@ -1035,7 +1047,7 @@
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
             if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
-                service.notifyGesture(gestureInfo);
+                service.notifyGesture(gestureEvent);
                 return true;
             }
         }
@@ -2193,6 +2205,9 @@
         }
         synchronized(mLock) {
             final UserState userState = getUserStateLocked(mCurrentUserId);
+            if (userState.mServiceToEnableWithShortcut == null) {
+                return null;
+            }
             return userState.mServiceToEnableWithShortcut.flattenToString();
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 82a593c..0038a27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -1463,6 +1463,20 @@
     }
 
     /**
+     * Returns the display ID according to given userId and windowId.
+     *
+     * @param userId The userId
+     * @param windowId The windowId
+     * @return The display ID
+     */
+    public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
+        final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+        final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+
+        return displayId;
+    }
+
+    /**
      * Gets current input focused window token from window manager, and returns its windowId.
      *
      * @param userId The userId
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 672518c..b9b2654 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -24,10 +24,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -191,7 +188,7 @@
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
         screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true, true, new Handler(Looper.getMainLooper()));
+                true, true, new Handler(Looper.getMainLooper()), null);
         return true;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
index 9101a01..7e8fb29 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
@@ -16,7 +16,7 @@
 
 package com.android.server.accessibility.gestures;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.content.Context;
 import android.gesture.GesturePoint;
@@ -119,11 +119,11 @@
         /**
          * Called when an event stream is recognized as a gesture.
          *
-         * @param gestureInfo Information about the gesture.
+         * @param gestureEvent Information about the gesture.
          *
          * @return true if the event is consumed, else false
          */
-        boolean onGestureCompleted(AccessibilityGestureInfo gestureInfo);
+        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
 
         /**
          * Called when the system has decided an event stream doesn't match any
@@ -567,19 +567,19 @@
             switch (direction) {
                 case LEFT:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_LEFT,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
                                     displayId));
                 case RIGHT:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_RIGHT,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
                                     displayId));
                 case UP:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_UP,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
                                     displayId));
                 case DOWN:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_DOWN,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
                                     displayId));
                 default:
                     // Do nothing.
@@ -600,7 +600,7 @@
             int segmentDirection1 = toDirection(dX1, dY1);
             int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
             return mListener.onGestureCompleted(
-                    new AccessibilityGestureInfo(gestureId, displayId));
+                    new AccessibilityGestureEvent(gestureId, displayId));
         }
         // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
         return mListener.onGestureCancelled(event, policyFlags);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
new file mode 100644
index 0000000..dc7a9aa
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
+import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.policy.WindowManagerPolicy;
+
+/**
+ * This class dispatches motion events and accessibility events relating to touch exploration and
+ * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is
+ * set correctly so that events go to the right place.
+ */
+class EventDispatcher {
+    private static final String LOG_TAG = "EventDispatcher";
+
+    private final AccessibilityManagerService mAms;
+    private Context mContext;
+    // The receiver of motion events.
+    private EventStreamTransformation mReceiver;
+    // Keep track of which pointers sent to the system are down.
+    private int mInjectedPointersDown;
+
+    // The time of the last injected down.
+    private long mLastInjectedDownEventTime;
+
+    // The last injected hover event.
+    private MotionEvent mLastInjectedHoverEvent;
+    private TouchState mState;
+
+    EventDispatcher(
+            Context context,
+            AccessibilityManagerService ams,
+            EventStreamTransformation receiver,
+            TouchState state) {
+        mContext = context;
+        mAms = ams;
+        mReceiver = receiver;
+        mState = state;
+    }
+
+    public void setReceiver(EventStreamTransformation receiver) {
+        mReceiver = receiver;
+    }
+
+    /**
+     * Sends an event.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param action The action of the event.
+     * @param pointerIdBits The bits of the pointers to send.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, int policyFlags) {
+        prototype.setAction(action);
+
+        MotionEvent event = null;
+        if (pointerIdBits == ALL_POINTER_ID_BITS) {
+            event = prototype;
+        } else {
+            try {
+                event = prototype.split(pointerIdBits);
+            } catch (IllegalArgumentException e) {
+                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
+                return;
+            }
+        }
+        if (action == MotionEvent.ACTION_DOWN) {
+            event.setDownTime(event.getEventTime());
+        } else {
+            event.setDownTime(getLastInjectedDownEventTime());
+        }
+        if (DEBUG) {
+            Slog.d(
+                    LOG_TAG,
+                    "Injecting event: "
+                            + event
+                            + ", policyFlags=0x"
+                            + Integer.toHexString(policyFlags));
+        }
+
+        // Make sure that the user will see the event.
+        policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+        // TODO: For now pass null for the raw event since the touch
+        //       explorer is the last event transformation and it does
+        //       not care about the raw event.
+        if (mReceiver != null) {
+            mReceiver.onMotionEvent(event, null, policyFlags);
+        } else {
+            Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
+        }
+        updateState(event);
+
+        if (event != prototype) {
+            event.recycle();
+        }
+    }
+
+    /**
+     * Sends an accessibility event of the given type.
+     *
+     * @param type The event type.
+     */
+    void sendAccessibilityEvent(int type) {
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
+        if (accessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            event.setWindowId(mAms.getActiveWindowId());
+            accessibilityManager.sendAccessibilityEvent(event);
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
+            }
+        }
+        // Todo: get rid of this and have TouchState control the sending of events rather than react
+        // to it.
+        mState.onInjectedAccessibilityEvent(type);
+    }
+
+    /**
+     * Processes an injected {@link MotionEvent} event.
+     *
+     * @param event The event to process.
+     */
+    void updateState(MotionEvent event) {
+        final int action = event.getActionMasked();
+        final int pointerId = event.getPointerId(event.getActionIndex());
+        final int pointerFlag = (1 << pointerId);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mInjectedPointersDown |= pointerFlag;
+                mLastInjectedDownEventTime = event.getDownTime();
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                mInjectedPointersDown &= ~pointerFlag;
+                if (mInjectedPointersDown == 0) {
+                    mLastInjectedDownEventTime = 0;
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mLastInjectedHoverEvent != null) {
+                    mLastInjectedHoverEvent.recycle();
+                }
+                mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                break;
+        }
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
+        }
+    }
+
+    /** Clears the internals state. */
+    public void clear() {
+        mInjectedPointersDown = 0;
+    }
+
+    /** @return The time of the last injected down event. */
+    public long getLastInjectedDownEventTime() {
+        return mLastInjectedDownEventTime;
+    }
+
+    /** @return The number of down pointers injected to the view hierarchy. */
+    public int getInjectedPointerDownCount() {
+        return Integer.bitCount(mInjectedPointersDown);
+    }
+
+    /** @return The bits of the injected pointers that are down. */
+    public int getInjectedPointersDown() {
+        return mInjectedPointersDown;
+    }
+
+    /**
+     * Whether an injected pointer is down.
+     *
+     * @param pointerId The unique pointer id.
+     * @return True if the pointer is down.
+     */
+    public boolean isInjectedPointerDown(int pointerId) {
+        final int pointerFlag = (1 << pointerId);
+        return (mInjectedPointersDown & pointerFlag) != 0;
+    }
+
+    /** @return The the last injected hover event. */
+    public MotionEvent getLastInjectedHoverEvent() {
+        return mLastInjectedHoverEvent;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("=========================");
+        builder.append("\nDown pointers #");
+        builder.append(Integer.bitCount(mInjectedPointersDown));
+        builder.append(" [ ");
+        for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+            if ((mInjectedPointersDown & i) != 0) {
+                builder.append(i);
+                builder.append(" ");
+            }
+        }
+        builder.append("]");
+        builder.append("\n=========================");
+        return builder.toString();
+    }
+
+    /**
+     * Computes the action for an injected event based on a masked action and a pointer index.
+     *
+     * @param actionMasked The masked action.
+     * @param pointerIndex The index of the pointer which has changed.
+     * @return The action to be used for injection.
+     */
+    private int computeInjectionAction(int actionMasked, int pointerIndex) {
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 0) {
+                    return MotionEvent.ACTION_DOWN;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_DOWN;
+                }
+            case MotionEvent.ACTION_POINTER_UP:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 1) {
+                    return MotionEvent.ACTION_UP;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_UP;
+                }
+            default:
+                return actionMasked;
+        }
+    }
+    /**
+     * Sends down events to the view hierarchy for all pointers which are not already being
+     * delivered i.e. pointers that are not yet injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
+
+        // Inject the injected pointers.
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Do not send event for already delivered pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                pointerIdBits |= (1 << pointerId);
+                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+            }
+        }
+    }
+
+    /**
+     * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
+     * pointers that are injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Skip non injected down pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                continue;
+            }
+            pointerIdBits |= (1 << pointerId);
+            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
+            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index d8b7e3a..f4ac821 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -20,7 +20,7 @@
 
 import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.content.Context;
 import android.graphics.Point;
 import android.os.Handler;
@@ -29,11 +29,11 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.BaseEventStreamTransformation;
+import com.android.server.accessibility.EventStreamTransformation;
 import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
@@ -61,17 +61,11 @@
 public class TouchExplorer extends BaseEventStreamTransformation
         implements AccessibilityGestureDetector.Listener {
 
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = false;
 
     // Tag for logging received events.
     private static final String LOG_TAG = "TouchExplorer";
 
-    // States this explorer can be in.
-    private static final int STATE_TOUCH_EXPLORING = 0x00000001;
-    private static final int STATE_DRAGGING = 0x00000002;
-    private static final int STATE_DELEGATING = 0x00000004;
-    private static final int STATE_GESTURE_DETECTING = 0x00000005;
-
     // The maximum of the cosine between the vectors of two moving
     // pointers so they can be considered moving in the same direction.
     private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
@@ -115,8 +109,7 @@
     // Helper class to track received pointers.
     private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
 
-    // Helper class to track injected pointers.
-    private final TouchState.InjectedPointerTracker mInjectedPointerTracker;
+    private final EventDispatcher mDispatcher;
 
     // Handle to the accessibility manager service.
     private final AccessibilityManagerService mAms;
@@ -154,7 +147,7 @@
         mAms = service;
         mState = new TouchState();
         mReceivedPointerTracker = mState.getReceivedPointerTracker();
-        mInjectedPointerTracker = mState.getInjectedPointerTracker();
+        mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState);
         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
         mHandler = new Handler(context.getMainLooper());
@@ -203,10 +196,10 @@
         }  else if (mState.isDragging()) {
             mDraggingPointerId = INVALID_POINTER_ID;
             // Send exit to all pointers that we have delivered.
-            sendUpForInjectedDownPointers(event, policyFlags);
+            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         } else if (mState.isDelegating()) {
             // Send exit to all pointers that we have delivered.
-            sendUpForInjectedDownPointers(event, policyFlags);
+            mDispatcher.sendUpForInjectedDownPointers(event, policyFlags);
         } else if (mState.isGestureDetecting()) {
             // No state specific cleanup required.
         }
@@ -248,7 +241,13 @@
             return;
         }
 
-        if (mState.isTouchExploring()) {
+        // TODO: extract the below functions into separate handlers for each state.
+        // Right now the number of functions and number of states make the code messy.
+        if (mState.isClear()) {
+            handleMotionEventStateClear(event, rawEvent, policyFlags);
+        } else if (mState.isTouchInteracting()) {
+            handleMotionEventStateTouchInteracting(event, rawEvent, policyFlags);
+        } else if (mState.isTouchExploring()) {
             handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
         } else if (mState.isDragging()) {
             handleMotionEventStateDragging(event, policyFlags);
@@ -271,7 +270,8 @@
         if (mSendTouchExplorationEndDelayed.isPending()
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
                     mSendTouchExplorationEndDelayed.cancel();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
+            mDispatcher.sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
         }
 
         // The event for touch interaction end should be strictly after the
@@ -279,15 +279,15 @@
         if (mSendTouchInteractionEndDelayed.isPending()
                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
             mSendTouchInteractionEndDelayed.cancel();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
         }
         super.onAccessibilityEvent(event);
     }
 
     @Override
     public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
-        // Ignore the event if we aren't touch exploring.
-        if (!mState.isTouchExploring()) {
+        // Ignore the event if we aren't touch interacting.
+        if (!mState.isTouchInteracting()) {
             return;
         }
 
@@ -304,8 +304,7 @@
 
     @Override
     public boolean onDoubleTap(MotionEvent event, int policyFlags) {
-        // Ignore the event if we aren't touch exploring.
-        if (!mState.isTouchExploring()) {
+        if (!mState.isTouchInteracting()) {
             return false;
         }
 
@@ -319,7 +318,7 @@
         }
 
         // Announce the end of a new touch interaction.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
         // Try to use the standard accessibility API to click
         if (!mAms.performActionOnAccessibilityFocusedItem(
@@ -339,19 +338,19 @@
         mExitGestureDetectionModeDelayed.post();
         // Send accessibility event to announce the start
         // of gesture recognition.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
         return false;
     }
 
     @Override
-    public boolean onGestureCompleted(AccessibilityGestureInfo gestureInfo) {
+    public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
         if (!mState.isGestureDetecting()) {
             return false;
         }
 
         endGestureDetection(true);
 
-        mAms.onGesture(gestureInfo);
+        mAms.onGesture(gestureEvent);
 
         return true;
     }
@@ -372,7 +371,8 @@
                 mSendHoverEnterAndMoveDelayed.addEvent(event);
                 mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                 mSendHoverExitDelayed.cancel();
-                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
                 return true;
             }
         }
@@ -380,6 +380,94 @@
     }
 
     /**
+     * Handles a motion event in the clear state i.e. no fingers are touching the screen.
+     */
+    private void handleMotionEventStateClear(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        switch (event.getActionMasked()) {
+            // The only way to leave the clear state is for a pointer to go down.
+            case MotionEvent.ACTION_DOWN:
+                handleActionDown(event, policyFlags);
+                break;
+            default:
+                // Some other nonsensical event.
+                break;
+        }
+    }
+
+    /**
+     * Handles ACTION_DOWN while in the clear or touch interacting states. This event represents the
+     * first finger touching the screen.
+     */
+    private void handleActionDown(MotionEvent event, int policyFlags) {
+        mAms.onTouchInteractionStart();
+
+        // If we still have not notified the user for the last
+        // touch, we figure out what to do. If were waiting
+        // we resent the delayed callback and wait again.
+        mSendHoverEnterAndMoveDelayed.cancel();
+        mSendHoverExitDelayed.cancel();
+
+        // If a touch exploration gesture is in progress send events for its end.
+        if (mState.isTouchExploring()) {
+            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+        }
+
+        // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double
+        // tap.
+        if (!mGestureDetector.firstTapDetected() && mState.isClear()) {
+            mSendTouchExplorationEndDelayed.forceSendAndRemove();
+            mSendTouchInteractionEndDelayed.forceSendAndRemove();
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
+        } else {
+            // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event.
+            mSendTouchInteractionEndDelayed.cancel();
+        }
+
+        if (!mGestureDetector.firstTapDetected() && !mState.isTouchExploring()) {
+            if (!mSendHoverEnterAndMoveDelayed.isPending()) {
+                // Queue a delayed transition to STATE_TOUCH_EXPLORING.
+                // If we do not detect that this is a gesture, delegation or drag the transition
+                // will fire by default.
+                // The idea is to avoid getting stuck in STATE_TOUCH_INTERACTING
+                final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+                final int pointerIdBits = (1 << pointerId);
+                mSendHoverEnterAndMoveDelayed.post(event, pointerIdBits, policyFlags);
+            } else {
+                // Cache the event until we discern exploration from gesturing.
+                mSendHoverEnterAndMoveDelayed.addEvent(event);
+            }
+        }
+    }
+
+    /**
+     * Handles a motion event in touch interacting state.
+     *
+     * @param event The event to be handled.
+     * @param rawEvent The raw (unmodified) motion event.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    private void handleMotionEventStateTouchInteracting(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                // Continue the previous interaction.
+                mSendTouchInteractionEndDelayed.cancel();
+                handleActionDown(event, policyFlags);
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                handleActionPointerDown();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_UP:
+                handleActionUp(event, policyFlags);
+                break;
+        }
+    }
+
+    /**
      * Handles a motion event in touch exploring state.
      *
      * @param event The event to be handled.
@@ -390,60 +478,19 @@
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                handleActionDownStateTouchExploring(event, policyFlags);
+                // We should have already received ACTION_DOWN. Ignore.
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
-                handleActionPointerDownStateTouchExploring();
+                handleActionPointerDown();
                 break;
             case MotionEvent.ACTION_MOVE:
                 handleActionMoveStateTouchExploring(event, rawEvent, policyFlags);
                 break;
             case MotionEvent.ACTION_UP:
-                handleActionUpStateTouchExploring(event, policyFlags);
+                handleActionUp(event, policyFlags);
                 break;
-        }
-    }
-
-    /**
-     * Handles ACTION_DOWN while in the default touch exploring state. This event represents the
-     * first finger touching the screen.
-     */
-    private void handleActionDownStateTouchExploring(MotionEvent event, int policyFlags) {
-        mAms.onTouchInteractionStart();
-
-        // If we still have not notified the user for the last
-        // touch, we figure out what to do. If were waiting
-        // we resent the delayed callback and wait again.
-        mSendHoverEnterAndMoveDelayed.cancel();
-        mSendHoverExitDelayed.cancel();
-
-        // If a touch exploration gesture is in progress send events for its end.
-        if (mState.isTouchExplorationInProgress()) {
-            sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-        }
-
-        // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double
-        // tap.
-        if (!mGestureDetector.firstTapDetected()) {
-            mSendTouchExplorationEndDelayed.forceSendAndRemove();
-            mSendTouchInteractionEndDelayed.forceSendAndRemove();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
-        } else {
-            // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event.
-            mSendTouchInteractionEndDelayed.cancel();
-        }
-
-        if (!mGestureDetector.firstTapDetected() && !mState.isTouchExplorationInProgress()) {
-            if (!mSendHoverEnterAndMoveDelayed.isPending()) {
-                // Deliver hover enter with a delay to have a chance
-                // to detect what the user is trying to do.
-                final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
-                final int pointerIdBits = (1 << pointerId);
-                mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags);
-            } else {
-                // Cache the event until we discern exploration from gesturing.
-                mSendHoverEnterAndMoveDelayed.addEvent(event);
-            }
+            default:
+                break;
         }
     }
 
@@ -451,7 +498,7 @@
      * Handles ACTION_POINTER_DOWN when in the touch exploring state. This event represents an
      * additional finger touching the screen.
      */
-    private void handleActionPointerDownStateTouchExploring() {
+    private void handleActionPointerDown() {
         // Another finger down means that if we have not started to deliver
         // hover events, we will not have to. The code for ACTION_MOVE will
         // decide what we will actually do next.
@@ -459,10 +506,10 @@
         mSendHoverExitDelayed.cancel();
     }
     /**
-     * Handles ACTION_MOVE while in the initial touch exploring state. This is where transitions to
+     * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to
      * delegating and dragging states are handled.
      */
-    private void handleActionMoveStateTouchExploring(
+    private void handleActionMoveStateTouchInteracting(
             MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
         final int pointerIndex = event.findPointerIndex(pointerId);
@@ -474,41 +521,14 @@
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
                     // Cache the event until we discern exploration from gesturing.
                     mSendHoverEnterAndMoveDelayed.addEvent(event);
-                } else if (mState.isTouchExplorationInProgress()) {
-                    sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                    sendMotionEvent(
-                            event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
                 }
                 break;
             case 2:
+                // Make sure we don't have any pending transitions to touch exploration
+                mSendHoverEnterAndMoveDelayed.cancel();
+                mSendHoverExitDelayed.cancel();
                 // More than one pointer so the user is not touch exploring
                 // and now we have to decide whether to delegate or drag.
-                if (mSendHoverEnterAndMoveDelayed.isPending()) {
-                    // We have not started sending events so cancel
-                    // scheduled sending events.
-                    mSendHoverEnterAndMoveDelayed.cancel();
-                    mSendHoverExitDelayed.cancel();
-                } else if (mState.isTouchExplorationInProgress()) {
-                    // If the user is touch exploring the second pointer may be
-                    // performing a double tap to activate an item without need
-                    // for the user to lift his exploring finger.
-                    // It is *important* to use the distance traveled by the pointers
-                    // on the screen which may or may not be magnified.
-                    final float deltaX =
-                            mReceivedPointerTracker.getReceivedPointerDownX(pointerId)
-                                    - rawEvent.getX(pointerIndex);
-                    final float deltaY =
-                            mReceivedPointerTracker.getReceivedPointerDownY(pointerId)
-                                    - rawEvent.getY(pointerIndex);
-                    final double moveDelta = Math.hypot(deltaX, deltaY);
-                    if (moveDelta < mDoubleTapSlop) {
-                        break;
-                    }
-                    // We are sending events so send exit and gesture
-                    // end since we transition to another state.
-                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-                }
-
                 // Remove move history before send injected non-move events
                 event = MotionEvent.obtainNoHistory(event);
                 if (isDraggingGesture(event)) {
@@ -517,44 +537,31 @@
                     mState.startDragging();
                     mDraggingPointerId = pointerId;
                     event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags());
-                    sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
                 } else {
                     // Two pointers moving arbitrary are delegated to the view hierarchy.
                     mState.startDelegating();
-                    sendDownForAllNotInjectedPointers(event, policyFlags);
+                    mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 }
                 break;
             default:
-                // More than one pointer so the user is not touch exploring
-                // and now we have to decide whether to delegate or drag.
-                if (mSendHoverEnterAndMoveDelayed.isPending()) {
-                    // We have not started sending events so cancel
-                    // scheduled sending events.
-                    mSendHoverEnterAndMoveDelayed.cancel();
-                    mSendHoverExitDelayed.cancel();
-                } else {
-                    // We are sending events so send exit and gesture
-                    // end since we transition to another state.
-                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
-                }
-
                 // More than two pointers are delegated to the view hierarchy.
                 mState.startDelegating();
                 event = MotionEvent.obtainNoHistory(event);
-                sendDownForAllNotInjectedPointers(event, policyFlags);
+                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 break;
         }
     }
 
     /**
-     * Handles ACTION_UP while in the initial touch exploring state. This event represents all
-     * fingers being lifted from the screen.
+     * Handles ACTION_UP while in the touch interacting state. This event represents all fingers
+     * being lifted from the screen.
      */
-    private void handleActionUpStateTouchExploring(MotionEvent event, int policyFlags) {
+    private void handleActionUp(MotionEvent event, int policyFlags) {
         mAms.onTouchInteractionEnd();
         final int pointerId = event.getPointerId(event.getActionIndex());
         final int pointerIdBits = (1 << pointerId);
-
         if (mSendHoverEnterAndMoveDelayed.isPending()) {
             // If we have not delivered the enter schedule an exit.
             mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
@@ -562,13 +569,70 @@
             // The user is touch exploring so we send events for end.
             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
         }
-
         if (!mSendTouchInteractionEndDelayed.isPending()) {
             mSendTouchInteractionEndDelayed.post();
         }
     }
 
     /**
+     * Handles move events while touch exploring. this is also where we drag or delegate based on
+     * the number of fingers moving on the screen.
+     */
+    private void handleActionMoveStateTouchExploring(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+        final int pointerIdBits = (1 << pointerId);
+        final int pointerIndex = event.findPointerIndex(pointerId);
+        switch (event.getPointerCount()) {
+            case 1:
+            // Touch exploration.
+                sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                break;
+            case 2:
+                if (mSendHoverEnterAndMoveDelayed.isPending()) {
+                    // We have not started sending events so cancel
+                    // scheduled sending events.
+                    mSendHoverEnterAndMoveDelayed.cancel();
+                    mSendHoverExitDelayed.cancel();
+                }
+                // If the user is touch exploring the second pointer may be
+                // performing a double tap to activate an item without need
+                // for the user to lift his exploring finger.
+                // It is *important* to use the distance traveled by the pointers
+                // on the screen which may or may not be magnified.
+                final float deltaX =
+                        mReceivedPointerTracker.getReceivedPointerDownX(pointerId)
+                                - rawEvent.getX(pointerIndex);
+                final float deltaY =
+                        mReceivedPointerTracker.getReceivedPointerDownY(pointerId)
+                                - rawEvent.getY(pointerIndex);
+                final double moveDelta = Math.hypot(deltaX, deltaY);
+                if (moveDelta > mDoubleTapSlop) {
+                    // The user is trying to either delegate or drag.
+                    handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
+                } else {
+                    // Otherwise the double tap will be handled by the gesture detector.
+                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+                }
+                break;
+            default:
+                // Three or more fingers is  something other than touch exploration.
+                if (mSendHoverEnterAndMoveDelayed.isPending()) {
+                    // We have not started sending events so cancel
+                    // scheduled sending events.
+                    mSendHoverEnterAndMoveDelayed.cancel();
+                    mSendHoverExitDelayed.cancel();
+                } else {
+                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+                }
+                handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
+                break;
+        }
+    }
+
+    /**
      * Handles a motion event in dragging state.
      *
      * @param event The event to be handled.
@@ -597,9 +661,10 @@
                 // goes down => delegate the three pointers to the view hierarchy
                 mState.startDelegating();
                 if (mDraggingPointerId != INVALID_POINTER_ID) {
-                    sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                 }
-                sendDownForAllNotInjectedPointers(event, policyFlags);
+                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
             } break;
             case MotionEvent.ACTION_MOVE: {
                 if (mDraggingPointerId == INVALID_POINTER_ID) {
@@ -611,21 +676,12 @@
                     } break;
                     case 2: {
                         if (isDraggingGesture(event)) {
-                            // Adjust event location to the middle location of the two pointers.
-                            final float firstPtrX = event.getX(0);
-                            final float firstPtrY = event.getY(0);
-                            final float secondPtrX = event.getX(1);
-                            final float secondPtrY = event.getY(1);
-                            final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
-                            final float deltaX =
-                                    (pointerIndex == 0) ? (secondPtrX - firstPtrX)
-                                            : (firstPtrX - secondPtrX);
-                            final float deltaY =
-                                    (pointerIndex == 0) ? (secondPtrY - firstPtrY)
-                                            : (firstPtrY - secondPtrY);
-                            event.offsetLocation(deltaX / 2, deltaY / 2);
                             // If still dragging send a drag event.
-                            sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
+                            adjustEventLocationForDrag(event);
+                            mDispatcher.sendMotionEvent(
+                                    event,
+                                    MotionEvent.ACTION_MOVE,
+                                    pointerIdBits,
                                     policyFlags);
                         } else {
                             // The two pointers are moving either in different directions or
@@ -634,20 +690,20 @@
                             // Remove move history before send injected non-move events
                             event = MotionEvent.obtainNoHistory(event);
                             // Send an event to the end of the drag gesture.
-                            sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                            mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                     policyFlags);
                             // Deliver all pointers to the view hierarchy.
-                            sendDownForAllNotInjectedPointers(event, policyFlags);
+                            mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                         }
                     } break;
                     default: {
                         mState.startDelegating();
                         event = MotionEvent.obtainNoHistory(event);
                         // Send an event to the end of the drag gesture.
-                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
+                        mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                 policyFlags);
                         // Deliver all pointers to the view hierarchy.
-                        sendDownForAllNotInjectedPointers(event, policyFlags);
+                        mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                     }
                 }
             } break;
@@ -655,22 +711,23 @@
                  final int pointerId = event.getPointerId(event.getActionIndex());
                  if (pointerId == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
-                     // Send an event to the end of the drag gesture.
-                     sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                        // Send an event to the end of the drag gesture.
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                  }
             } break;
             case MotionEvent.ACTION_UP: {
                 mAms.onTouchInteractionEnd();
                 // Announce the end of a new touch interaction.
-                sendAccessibilityEvent(
+                mDispatcher.sendAccessibilityEvent(
                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
                 final int pointerId = event.getPointerId(event.getActionIndex());
                 if (pointerId == mDraggingPointerId) {
                     mDraggingPointerId = INVALID_POINTER_ID;
                     // Send an event to the end of the drag gesture.
-                    sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
+                    mDispatcher.sendMotionEvent(
+                            event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                 }
-                mState.startTouchExploring();
             } break;
         }
     }
@@ -691,17 +748,18 @@
             }
             case MotionEvent.ACTION_UP: {
                 // Deliver the event.
-                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
 
                 // Announce the end of a the touch interaction.
                 mAms.onTouchInteractionEnd();
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+                mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
-                mState.startTouchExploring();
             } break;
             default: {
-                // Deliver the event.
-                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+                    // Deliver the event.
+                mDispatcher.sendMotionEvent(
+                        event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
             }
         }
     }
@@ -710,53 +768,15 @@
         mAms.onTouchInteractionEnd();
 
         // Announce the end of the gesture recognition.
-        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+        mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
         // Don't announce the end of a the touch interaction if users didn't lift their fingers.
         if (interactionEnd) {
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
         }
 
         mExitGestureDetectionModeDelayed.cancel();
-        mState.startTouchExploring();
     }
 
-    /**
-     * Sends an accessibility event of the given type.
-     *
-     * @param type The event type.
-     */
-    private void sendAccessibilityEvent(int type) {
-        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
-        if (accessibilityManager.isEnabled()) {
-            AccessibilityEvent event = AccessibilityEvent.obtain(type);
-            event.setWindowId(mAms.getActiveWindowId());
-            accessibilityManager.sendAccessibilityEvent(event);
-            mState.onInjectedAccessibilityEvent(type);
-        }
-    }
-
-    /**
-     * Sends down events to the view hierarchy for all pointers which are
-     * not already being delivered i.e. pointers that are not yet injected.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
-
-        // Inject the injected pointers.
-        int pointerIdBits = 0;
-        final int pointerCount = prototype.getPointerCount();
-        for (int i = 0; i < pointerCount; i++) {
-            final int pointerId = prototype.getPointerId(i);
-            // Do not send event for already delivered pointers.
-            if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
-                pointerIdBits |= (1 << pointerId);
-                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
-                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
-            }
-        }
-    }
 
     /**
      * Sends the exit events if needed. Such events are hover exit and touch explore
@@ -765,13 +785,14 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
-        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
             if (!mSendTouchExplorationEndDelayed.isPending()) {
                 mSendTouchExplorationEndDelayed.post();
             }
-            sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
+            mDispatcher.sendMotionEvent(
+                    event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
         }
     }
 
@@ -782,115 +803,14 @@
      * @param policyFlags The policy flags associated with the event.
      */
     private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
-        MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
+        MotionEvent event = mDispatcher.getLastInjectedHoverEvent();
         if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
             final int pointerIdBits = event.getPointerIdBits();
-            sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
+            mDispatcher.sendMotionEvent(
+                    event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
         }
     }
 
-    /**
-     * Sends up events to the view hierarchy for all pointers which are
-     * already being delivered i.e. pointers that are injected.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
-        int pointerIdBits = 0;
-        final int pointerCount = prototype.getPointerCount();
-        for (int i = 0; i < pointerCount; i++) {
-            final int pointerId = prototype.getPointerId(i);
-            // Skip non injected down pointers.
-            if (!mInjectedPointerTracker.isInjectedPointerDown(pointerId)) {
-                continue;
-            }
-            pointerIdBits |= (1 << pointerId);
-            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
-            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
-        }
-    }
-
-    /**
-     * Sends an event.
-     *
-     * @param prototype The prototype from which to create the injected events.
-     * @param action The action of the event.
-     * @param pointerIdBits The bits of the pointers to send.
-     * @param policyFlags The policy flags associated with the event.
-     */
-    private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
-            int policyFlags) {
-        prototype.setAction(action);
-
-        MotionEvent event = null;
-        if (pointerIdBits == ALL_POINTER_ID_BITS) {
-            event = prototype;
-        } else {
-            try {
-                event = prototype.split(pointerIdBits);
-            } catch (IllegalArgumentException e) {
-                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
-                return;
-            }
-        }
-        if (action == MotionEvent.ACTION_DOWN) {
-            event.setDownTime(event.getEventTime());
-        } else {
-            event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
-        }
-        if (DEBUG) {
-            Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
-                    + Integer.toHexString(policyFlags));
-        }
-
-        // Make sure that the user will see the event.
-        policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-        // TODO: For now pass null for the raw event since the touch
-        //       explorer is the last event transformation and it does
-        //       not care about the raw event.
-        super.onMotionEvent(event, null, policyFlags);
-
-        mInjectedPointerTracker.onMotionEvent(event);
-
-        if (event != prototype) {
-            event.recycle();
-        }
-    }
-
-    /**
-     * Computes the action for an injected event based on a masked action
-     * and a pointer index.
-     *
-     * @param actionMasked The masked action.
-     * @param pointerIndex The index of the pointer which has changed.
-     * @return The action to be used for injection.
-     */
-    private int computeInjectionAction(int actionMasked, int pointerIndex) {
-        switch (actionMasked) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_POINTER_DOWN: {
-                // Compute the action based on how many down pointers are injected.
-                if (mInjectedPointerTracker.getInjectedPointerDownCount() == 0) {
-                    return MotionEvent.ACTION_DOWN;
-                } else {
-                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
-                        | MotionEvent.ACTION_POINTER_DOWN;
-                }
-            }
-            case MotionEvent.ACTION_POINTER_UP: {
-                // Compute the action based on how many down pointers are injected.
-                if (mInjectedPointerTracker.getInjectedPointerDownCount() == 1) {
-                    return MotionEvent.ACTION_UP;
-                } else {
-                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
-                        | MotionEvent.ACTION_POINTER_UP;
-                }
-            }
-            default:
-                return actionMasked;
-        }
-    }
 
     /**
      * Determines whether a two pointer gesture is a dragging one.
@@ -916,6 +836,34 @@
     }
 
     /**
+     * Adjust the location of an injected event when performing a drag The new location will be in
+     * between the two fingers touching the screen.
+     */
+    private void adjustEventLocationForDrag(MotionEvent event) {
+
+        final float firstPtrX = event.getX(0);
+        final float firstPtrY = event.getY(0);
+        final float secondPtrX = event.getX(1);
+        final float secondPtrY = event.getY(1);
+        final int pointerIndex = event.findPointerIndex(mDraggingPointerId);
+        final float deltaX =
+                (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX);
+        final float deltaY =
+                (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY);
+        event.offsetLocation(deltaX / 2, deltaY / 2);
+    }
+
+    public TouchState getState() {
+        return mState;
+    }
+
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mDispatcher.setReceiver(next);
+        super.setNext(next);
+    }
+
+    /**
      * Class for delayed exiting from gesture detecting mode.
      */
     private final class ExitGestureDetectionModeDelayed implements Runnable {
@@ -931,7 +879,7 @@
         @Override
         public void run() {
             // Announce the end of gesture recognition.
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
             clear();
         }
     }
@@ -947,8 +895,7 @@
         private int mPointerIdBits;
         private int mPolicyFlags;
 
-        public void post(MotionEvent event, boolean touchExplorationInProgress,
-                int pointerIdBits, int policyFlags) {
+        public void post(MotionEvent event, int pointerIdBits, int policyFlags) {
             cancel();
             addEvent(event);
             mPointerIdBits = pointerIdBits;
@@ -989,11 +936,12 @@
 
         public void run() {
             // Send an accessibility event to announce the touch exploration start.
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
+            mDispatcher.sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
 
             if (!mEvents.isEmpty()) {
                 // Deliver a down event.
-                sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
+                mDispatcher.sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER,
                         mPointerIdBits, mPolicyFlags);
                 if (DEBUG) {
                     Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1003,7 +951,7 @@
                 // Deliver move events.
                 final int eventCount = mEvents.size();
                 for (int i = 1; i < eventCount; i++) {
-                    sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
+                    mDispatcher.sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE,
                             mPointerIdBits, mPolicyFlags);
                     if (DEBUG) {
                         Slog.d(LOG_TAG_SEND_HOVER_DELAYED,
@@ -1063,7 +1011,7 @@
                 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:"
                         + " ACTION_HOVER_EXIT");
             }
-            sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
+            mDispatcher.sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT,
                     mPointerIdBits, mPolicyFlags);
             if (!mSendTouchExplorationEndDelayed.isPending()) {
                 mSendTouchExplorationEndDelayed.cancel();
@@ -1107,7 +1055,7 @@
 
         @Override
         public void run() {
-            sendAccessibilityEvent(mEventType);
+            mDispatcher.sendAccessibilityEvent(mEventType);
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index 820c1a7..49938fa 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -18,6 +18,8 @@
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
 import android.annotation.IntDef;
 import android.util.Slog;
 import android.view.MotionEvent;
@@ -29,60 +31,56 @@
  * dispatch.
  */
 public class TouchState {
-
-    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "TouchState";
     // Pointer-related constants
     // This constant captures the current implementation detail that
     // pointer IDs are between 0 and 31 inclusive (subject to change).
     // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
-    private static final int MAX_POINTER_COUNT = 32;
+    static final int MAX_POINTER_COUNT = 32;
     // Constant referring to the ids bits of all pointers.
     public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
 
     // States that the touch explorer can be in.
-    public static final int STATE_TOUCH_EXPLORING = 0x00000001;
-    public static final int STATE_DRAGGING = 0x00000002;
-    public static final int STATE_DELEGATING = 0x00000003;
-    public static final int STATE_GESTURE_DETECTING = 0x00000004;
+    // In the clear state the user is not touching the screen.
+    public static final int STATE_CLEAR = 0;
+    // The user is touching the screen and we are trying to figure out their intent.
+    // This state gets its name from the TYPE_TOUCH_INTERACTION start and end accessibility events.
+    public static final int STATE_TOUCH_INTERACTING = 1;
+    // The user is explicitly exploring the screen.
+    public static final int STATE_TOUCH_EXPLORING = 2;
+    // the user is dragging with two fingers.
+    public static final int STATE_DRAGGING = 3;
+    // The user is performing some other two finger gesture which we pass through to the view
+    // hierarchy as a one-finger gesture e.g. two-finger scrolling.
+    public static final int STATE_DELEGATING = 4;
+    // The user is performing something that might be a gesture.
+    public static final int STATE_GESTURE_DETECTING = 5;
 
-    @IntDef({STATE_TOUCH_EXPLORING, STATE_DRAGGING, STATE_DELEGATING, STATE_GESTURE_DETECTING})
+    @IntDef({
+        STATE_CLEAR,
+        STATE_TOUCH_INTERACTING,
+        STATE_TOUCH_EXPLORING,
+        STATE_DRAGGING,
+        STATE_DELEGATING,
+        STATE_GESTURE_DETECTING
+    })
     public @interface State {}
 
     // The current state of the touch explorer.
-    private int mState = STATE_TOUCH_EXPLORING;
-    // Whether touch exploration is in progress.
-    // TODO: Add separate states to represent  intend detection and actual touch exploration so that
-    // only one variable describes the state.
-    private boolean mTouchExplorationInProgress;
+    private int mState = STATE_CLEAR;
     // Helper class to track received pointers.
     // Todo: collapse or hide this class so multiple classes don't modify it.
     private final ReceivedPointerTracker mReceivedPointerTracker;
-    // Helper class to track injected pointers.
-    // Todo: collapse or hide this class so multiple classes don't modify it.
-    private final InjectedPointerTracker mInjectedPointerTracker;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
-        mInjectedPointerTracker = new InjectedPointerTracker();
     }
 
     /** Clears the internal shared state. */
     public void clear() {
-        mState = STATE_TOUCH_EXPLORING;
-        mTouchExplorationInProgress = false;
+        setState(STATE_CLEAR);
         // Reset the pointer trackers.
         mReceivedPointerTracker.clear();
-        mInjectedPointerTracker.clear();
-    }
-
-    /**
-     * Updates the state in response to a hover event dispatched by TouchExplorer.
-     *
-     * @param event The event sent from TouchExplorer.
-     */
-    public void onInjectedMotionEvent(MotionEvent event) {
-        mInjectedPointerTracker.onMotionEvent(event);
     }
 
     /**
@@ -94,18 +92,33 @@
         mReceivedPointerTracker.onMotionEvent(rawEvent);
     }
 
-    /**
-     * Updates the state in response to an accessibility event being sent from TouchExplorer.
-     *
-     * @param type The event type.
-     */
     public void onInjectedAccessibilityEvent(int type) {
+        // The below state transitions go here because the related events are often sent on a
+        // delay.
+        // This allows state to accurately reflect the state in the moment.
+        // TODO: replaced the delayed event senders with delayed state transitions
+        // so that state transitions trigger events rather than events triggering state
+        // transitions.
         switch (type) {
+            case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
+                startTouchInteracting();
+                break;
+            case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
+                clear();
+                break;
             case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
-                mTouchExplorationInProgress = true;
+                startTouchExploring();
                 break;
             case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
-                mTouchExplorationInProgress = false;
+                startTouchInteracting();
+                break;
+            case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
+                startGestureDetecting();
+                break;
+            case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
+                startTouchInteracting();
+                break;
+            default:
                 break;
         }
     }
@@ -117,6 +130,7 @@
 
     /** Transitions to a new state. */
     public void setState(@State int state) {
+        if (mState == state) return;
         if (DEBUG) {
             Slog.i(LOG_TAG, getStateSymbolicName(mState) + "->" + getStateSymbolicName(state));
         }
@@ -159,26 +173,32 @@
         setState(STATE_DRAGGING);
     }
 
-    public boolean isTouchExplorationInProgress() {
-        return mTouchExplorationInProgress;
+    public boolean isTouchInteracting() {
+        return mState == STATE_TOUCH_INTERACTING;
     }
 
-    public void setTouchExplorationInProgress(boolean touchExplorationInProgress) {
-        mTouchExplorationInProgress = touchExplorationInProgress;
+    /**
+     * Transitions to the touch interacting state, where we attempt to figure out what the user is
+     * doing.
+     */
+    public void startTouchInteracting() {
+        setState(STATE_TOUCH_INTERACTING);
     }
 
+    public boolean isClear() {
+        return mState == STATE_CLEAR;
+    }
     /** Returns a string representation of the current state. */
     public String toString() {
-        return "TouchState { "
-                + "mState: "
-                + getStateSymbolicName(mState)
-                + ", mTouchExplorationInProgress"
-                + mTouchExplorationInProgress
-                + " }";
+        return "TouchState { " + "mState: " + getStateSymbolicName(mState) + " }";
     }
     /** Returns a string representation of the specified state. */
     public static String getStateSymbolicName(int state) {
         switch (state) {
+            case STATE_CLEAR:
+                return "STATE_CLEAR";
+            case STATE_TOUCH_INTERACTING:
+                return "STATE_TOUCH_INTERACTING";
             case STATE_TOUCH_EXPLORING:
                 return "STATE_TOUCH_EXPLORING";
             case STATE_DRAGGING:
@@ -192,117 +212,10 @@
         }
     }
 
-    public InjectedPointerTracker getInjectedPointerTracker() {
-        return mInjectedPointerTracker;
-    }
-
     public ReceivedPointerTracker getReceivedPointerTracker() {
         return mReceivedPointerTracker;
     }
 
-    /** This class tracks the up/down state of each pointer. It does not track movement. */
-    class InjectedPointerTracker {
-        private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
-
-        // Keep track of which pointers sent to the system are down.
-        private int mInjectedPointersDown;
-
-        // The time of the last injected down.
-        private long mLastInjectedDownEventTime;
-
-        // The last injected hover event.
-        private MotionEvent mLastInjectedHoverEvent;
-
-        /**
-         * Processes an injected {@link MotionEvent} event.
-         *
-         * @param event The event to process.
-         */
-        public void onMotionEvent(MotionEvent event) {
-            final int action = event.getActionMasked();
-            final int pointerId = event.getPointerId(event.getActionIndex());
-            final int pointerFlag = (1 << pointerId);
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    mInjectedPointersDown |= pointerFlag;
-                    mLastInjectedDownEventTime = event.getDownTime();
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_POINTER_UP:
-                    mInjectedPointersDown &= ~pointerFlag;
-                    if (mInjectedPointersDown == 0) {
-                        mLastInjectedDownEventTime = 0;
-                    }
-                    break;
-                case MotionEvent.ACTION_HOVER_ENTER:
-                case MotionEvent.ACTION_HOVER_MOVE:
-                case MotionEvent.ACTION_HOVER_EXIT:
-                    if (mLastInjectedHoverEvent != null) {
-                        mLastInjectedHoverEvent.recycle();
-                    }
-                    mLastInjectedHoverEvent = MotionEvent.obtain(event);
-                    break;
-            }
-            if (DEBUG) {
-                Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
-            }
-        }
-
-        /** Clears the internals state. */
-        public void clear() {
-            mInjectedPointersDown = 0;
-        }
-
-        /** @return The time of the last injected down event. */
-        public long getLastInjectedDownEventTime() {
-            return mLastInjectedDownEventTime;
-        }
-
-        /** @return The number of down pointers injected to the view hierarchy. */
-        public int getInjectedPointerDownCount() {
-            return Integer.bitCount(mInjectedPointersDown);
-        }
-
-        /** @return The bits of the injected pointers that are down. */
-        public int getInjectedPointersDown() {
-            return mInjectedPointersDown;
-        }
-
-        /**
-         * Whether an injected pointer is down.
-         *
-         * @param pointerId The unique pointer id.
-         * @return True if the pointer is down.
-         */
-        public boolean isInjectedPointerDown(int pointerId) {
-            final int pointerFlag = (1 << pointerId);
-            return (mInjectedPointersDown & pointerFlag) != 0;
-        }
-
-        /** @return The the last injected hover event. */
-        public MotionEvent getLastInjectedHoverEvent() {
-            return mLastInjectedHoverEvent;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder builder = new StringBuilder();
-            builder.append("=========================");
-            builder.append("\nDown pointers #");
-            builder.append(Integer.bitCount(mInjectedPointersDown));
-            builder.append(" [ ");
-            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
-                if ((mInjectedPointersDown & i) != 0) {
-                    builder.append(i);
-                    builder.append(" ");
-                }
-            }
-            builder.append("]");
-            builder.append("\n=========================");
-            return builder.toString();
-        }
-    }
     /** This class tracks where and when a pointer went down. It does not track its movement. */
     class ReceivedPointerTracker {
         private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index d7e68f8..5844f98 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTargetEvent;
@@ -61,7 +62,8 @@
 
     public AppPredictionManagerService(Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
-                com.android.internal.R.string.config_defaultAppPredictionService), null);
+                com.android.internal.R.string.config_defaultAppPredictionService), null,
+                PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
     }
 
@@ -80,6 +82,22 @@
         getContext().enforceCallingPermission(MANAGE_APP_PREDICTIONS, TAG);
     }
 
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+        final AppPredictionPerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageUpdatedLocked();
+        }
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        final AppPredictionPerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageRestartedLocked();
+        }
+    }
+
     @Override
     protected int getMaximumTemporaryServiceDurationMs() {
         return MAX_TEMP_SERVICE_DURATION_MS;
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 03c4542..4f49fb7 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -251,6 +251,40 @@
         // Do nothing, eventually the system will bind to the remote service again...
     }
 
+    void onPackageUpdatedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageUpdatedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    void onPackageRestartedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageRestartedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    private void destroyAndRebindRemoteService() {
+        if (mRemoteService == null) {
+            return;
+        }
+
+        if (isDebug()) {
+            Slog.d(TAG, "Destroying the old remote service.");
+        }
+        mRemoteService.destroy();
+        mRemoteService = null;
+
+        mRemoteService = getRemoteServiceLocked();
+        if (mRemoteService != null) {
+            if (isDebug()) {
+                Slog.d(TAG, "Rebinding to the new remote service.");
+            }
+            mRemoteService.reconnect();
+        }
+    }
+
     /**
      * Called after the remote service connected, it's used to restore state from a 'zombie'
      * service (i.e., after it died).
diff --git a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
index c82e7a0..04e0e7f 100644
--- a/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
+++ b/services/appprediction/java/com/android/server/appprediction/RemoteAppPredictionService.java
@@ -135,6 +135,13 @@
     }
 
     /**
+     * Schedules a request to bind to the remote service.
+     */
+    public void reconnect() {
+        super.scheduleBind();
+    }
+
+    /**
      * Failure callback
      */
     public interface RemoteAppPredictionServiceCallbacks
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 39866a7..fea4e90 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.UserIdInt;
@@ -856,13 +857,13 @@
             ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
             LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
             for (int i = 0; i < N; i++) {
-                if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
-                    // We key the updates based on request id, so that the values are sorted in the
-                    // order they were received.
-                    int M = updatesMap.size();
-                    for (int j = 0; j < M; j++) {
-                        outUpdates.add(updatesMap.valueAt(j));
-                    }
+                updatesMap.clear();
+                host.getPendingUpdatesForId(appWidgetIds[i], updatesMap);
+                // We key the updates based on request id, so that the values are sorted in the
+                // order they were received.
+                int m = updatesMap.size();
+                for (int j = 0; j < m; j++) {
+                    outUpdates.add(updatesMap.valueAt(j));
                 }
             }
             // Reset the update counter once all the updates have been calculated
@@ -2102,6 +2103,40 @@
         }
     }
 
+    private void scheduleNotifyAppWidgetRemovedLocked(Widget widget) {
+        long requestId = UPDATE_COUNTER.incrementAndGet();
+        if (widget != null) {
+            widget.updateSequenceNos.clear();
+        }
+        if (widget == null || widget.provider == null || widget.provider.zombie
+                || widget.host.callbacks == null || widget.host.zombie) {
+            return;
+        }
+
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = widget.host;
+        args.arg2 = widget.host.callbacks;
+        args.arg3 = requestId;
+        args.argi1 = widget.appWidgetId;
+
+        mCallbackHandler.obtainMessage(
+            CallbackHandler.MSG_NOTIFY_APP_WIDGET_REMOVED,
+            args).sendToTarget();
+    }
+
+    private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId,
+            long requestId) {
+        try {
+            callbacks.appWidgetRemoved(appWidgetId);
+            host.lastWidgetUpdateSequenceNo = requestId;
+        } catch (RemoteException re) {
+            synchronized (mLock) {
+                Slog.e(TAG, "Widget host dead: " + host.id, re);
+                host.callbacks = null;
+            }
+        }
+    }
+
     private void scheduleNotifyGroupHostsForProvidersChangedLocked(int userId) {
         final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
 
@@ -2870,8 +2905,8 @@
      */
     void removeWidgetLocked(Widget widget) {
         mWidgets.remove(widget);
-
         onWidgetRemovedLocked(widget);
+        scheduleNotifyAppWidgetRemovedLocked(widget);
     }
 
     private void onWidgetRemovedLocked(Widget widget) {
@@ -3587,6 +3622,7 @@
         public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
         public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
         public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
+        public static final int MSG_NOTIFY_APP_WIDGET_REMOVED = 5;
 
         public CallbackHandler(Looper looper) {
             super(looper, null, false);
@@ -3619,6 +3655,16 @@
                     handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestId);
                 } break;
 
+                case MSG_NOTIFY_APP_WIDGET_REMOVED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    Host host = (Host) args.arg1;
+                    IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
+                    long requestId = (Long) args.arg3;
+                    final int appWidgetId = args.argi1;
+                    args.recycle();
+                    handleNotifyAppWidgetRemoved(host, callbacks, appWidgetId, requestId);
+                } break;
+
                 case MSG_NOTIFY_PROVIDERS_CHANGED: {
                     SomeArgs args = (SomeArgs) message.obj;
                     Host host = (Host) args.arg1;
@@ -4017,14 +4063,13 @@
         /**
          * Adds all pending updates in {@param outUpdates} keys by the update time.
          */
-        public boolean getPendingUpdatesForId(int appWidgetId,
+        public void getPendingUpdatesForId(int appWidgetId,
                 LongSparseArray<PendingHostUpdate> outUpdates) {
             long updateSequenceNo = lastWidgetUpdateSequenceNo;
             int N = widgets.size();
             for (int i = 0; i < N; i++) {
                 Widget widget = widgets.get(i);
                 if (widget.appWidgetId == appWidgetId) {
-                    outUpdates.clear();
                     for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) {
                         long requestId = widget.updateSequenceNos.valueAt(j);
                         if (requestId <= updateSequenceNo) {
@@ -4046,10 +4091,11 @@
                         }
                         outUpdates.put(requestId, update);
                     }
-                    return true;
+                    return;
                 }
             }
-            return false;
+            outUpdates.put(lastWidgetUpdateSequenceNo,
+                    PendingHostUpdate.appWidgetRemoved(appWidgetId));
         }
 
         @Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6b7c3e6..c689ed1 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -212,8 +212,7 @@
                 (u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
 
         if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
-            final UserManager um = getContext().getSystemService(UserManager.class);
-            final List<UserInfo> users = um.getUsers();
+            final List<UserInfo> users = getSupportedUsers();
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
                 // Must eager load the services so they bind to the augmented autofill service
@@ -325,6 +324,11 @@
     }
 
     @Override // from SystemService
+    public boolean isSupported(UserInfo userInfo) {
+        return userInfo.isFull() || userInfo.isManagedProfile();
+    }
+
+    @Override // from SystemService
     public void onSwitchUser(int userHandle) {
         if (sDebug) Slog.d(TAG, "Hiding UI when user switched");
         mUi.hideAll(null);
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index ef03d83..a3b0c89 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -2,4 +2,5 @@
     name: "services.backup",
     srcs: ["java/**/*.java"],
     libs: ["services.core"],
+    static_libs: ["backuplib"],
 }
diff --git a/services/backup/backuplib/Android.bp b/services/backup/backuplib/Android.bp
new file mode 100644
index 0000000..7b194a092
--- /dev/null
+++ b/services/backup/backuplib/Android.bp
@@ -0,0 +1,5 @@
+java_library {
+    name: "backuplib",
+    srcs: ["java/**/*.java"],
+    libs: ["services.core"],
+}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/TransportManager.java
rename to services/backup/backuplib/java/com/android/server/backup/TransportManager.java
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
new file mode 100644
index 0000000..ab87080
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Delegates all transport methods to the delegate() implemented in the derived class.
+ */
+public abstract class DelegatingTransport extends IBackupTransport.Stub {
+    protected abstract IBackupTransport getDelegate() throws RemoteException;
+
+    /**
+     * Ask the transport for the name under which it should be registered.  This will
+     * typically be its host service's component name, but need not be.
+     */
+    @Override
+    public String name() throws RemoteException {
+        return getDelegate().name();
+    }
+
+    /**
+     * Ask the transport for an Intent that can be used to launch any internal
+     * configuration Activity that it wishes to present.  For example, the transport
+     * may offer a UI for allowing the user to supply login credentials for the
+     * transport's off-device backend.
+     *
+     * If the transport does not supply any user-facing configuration UI, it should
+     * return null from this method.
+     *
+     * @return An Intent that can be passed to Context.startActivity() in order to
+     * launch the transport's configuration UI.  This method will return null
+     * if the transport does not offer any user-facing configuration UI.
+     */
+    @Override
+    public Intent configurationIntent() throws RemoteException {
+        return getDelegate().configurationIntent();
+    }
+
+    /**
+     * On demand, supply a one-line string that can be shown to the user that
+     * describes the current backend destination.  For example, a transport that
+     * can potentially associate backup data with arbitrary user accounts should
+     * include the name of the currently-active account here.
+     *
+     * @return A string describing the destination to which the transport is currently
+     * sending data.  This method should not return null.
+     */
+    @Override
+    public String currentDestinationString() throws RemoteException {
+        return getDelegate().currentDestinationString();
+    }
+
+    /**
+     * Ask the transport for an Intent that can be used to launch a more detailed
+     * secondary data management activity.  For example, the configuration intent might
+     * be one for allowing the user to select which account they wish to associate
+     * their backups with, and the management intent might be one which presents a
+     * UI for managing the data on the backend.
+     *
+     * <p>In the Settings UI, the configuration intent will typically be invoked
+     * when the user taps on the preferences item labeled with the current
+     * destination string, and the management intent will be placed in an overflow
+     * menu labelled with the management label string.
+     *
+     * <p>If the transport does not supply any user-facing data management
+     * UI, then it should return {@code null} from this method.
+     *
+     * @return An intent that can be passed to Context.startActivity() in order to
+     * launch the transport's data-management UI.  This method will return
+     * {@code null} if the transport does not offer any user-facing data
+     * management UI.
+     */
+    @Override
+    public Intent dataManagementIntent() throws RemoteException {
+        return getDelegate().dataManagementIntent();
+    }
+
+    /**
+     * On demand, supply a short {@link CharSequence} that can be shown to the user as the
+     * label on
+     * an overflow menu item used to invoke the data management UI.
+     *
+     * @return A {@link CharSequence} to be used as the label for the transport's data management
+     *         affordance.  If the transport supplies a data management intent, this
+     *         method must not return {@code null}.
+     */
+    @Override
+    public CharSequence dataManagementIntentLabel() throws RemoteException {
+        return getDelegate().dataManagementIntentLabel();
+    }
+
+    /**
+     * Ask the transport where, on local device storage, to keep backup state blobs.
+     * This is per-transport so that mock transports used for testing can coexist with
+     * "live" backup services without interfering with the live bookkeeping.  The
+     * returned string should be a name that is expected to be unambiguous among all
+     * available backup transports; the name of the class implementing the transport
+     * is a good choice.  This MUST be constant.
+     *
+     * @return A unique name, suitable for use as a file or directory name, that the
+     *         Backup Manager could use to disambiguate state files associated with
+     *         different backup transports.
+     */
+    @Override
+    public String transportDirName() throws RemoteException {
+        return getDelegate().transportDirName();
+    }
+
+    /**
+     * Verify that this is a suitable time for a backup pass.  This should return zero
+     * if a backup is reasonable right now, some positive value otherwise.  This method
+     * will be called outside of the {@link #startSession}/{@link #endSession} pair.
+     *
+     * <p>If this is not a suitable time for a backup, the transport should return a
+     * backoff delay, in milliseconds, after which the Backup Manager should try again.
+     *
+     * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+     *   in milliseconds to suggest deferring the backup pass for a while.
+     */
+    @Override
+    public long requestBackupTime() throws RemoteException {
+        return getDelegate().requestBackupTime();
+    }
+
+    /**
+     * Initialize the server side storage for this device, erasing all stored data.
+     * The transport may send the request immediately, or may buffer it.  After
+     * this is called, {@link #finishBackup} must be called to ensure the request
+     * is sent and received successfully.
+     *
+     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
+     *   {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
+     */
+    @Override
+    public int initializeDevice() throws RemoteException {
+        return getDelegate().initializeDevice();
+    }
+
+    /**
+     * Send one application's data to the backup destination.  The transport may send
+     * the data immediately, or may buffer it.  After this is called, {@link #finishBackup}
+     * must be called to ensure the data is sent and recorded successfully.
+     *
+     * @param packageInfo The identity of the application whose data is being backed up.
+     *   This specifically includes the signature list for the package.
+     * @param inFd Descriptor of file with data that resulted from invoking the application's
+     *   BackupService.doBackup() method.  This may be a pipe rather than a file on
+     *   persistent media, so it may not be seekable.
+     * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
+     * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
+     *  {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
+     *  {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+     *  become lost due to inactive expiry or some other reason and needs re-initializing)
+     */
+    @Override
+    public int performBackup(PackageInfo packageInfo,
+            ParcelFileDescriptor inFd, int flags) throws RemoteException {
+        return getDelegate().performBackup(packageInfo, inFd, flags);
+    }
+
+    /**
+     * Erase the give application's data from the backup destination.  This clears
+     * out the given package's data from the current backup set, making it as though
+     * the app had never yet been backed up.  After this is called, {@link finishBackup}
+     * must be called to ensure that the operation is recorded successfully.
+     *
+     * @return the same error codes as {@link #performBackup}.
+     * @param packageInfo
+     */
+    @Override
+    public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+        return getDelegate().clearBackupData(packageInfo);
+    }
+
+    /**
+     * Finish sending application data to the backup destination.  This must be
+     * called after {@link #performBackup} or {@link clearBackupData} to ensure that
+     * all data is sent.  Only when this method returns true can a backup be assumed
+     * to have succeeded.
+     *
+     * @return the same error codes as {@link #performBackup}.
+     */
+    @Override
+    public int finishBackup() throws RemoteException {
+        return getDelegate().finishBackup();
+    }
+
+    /**
+     * Get the set of all backups currently available over this transport.
+     *
+     * @return Descriptions of the set of restore images available for this device,
+     *   or null if an error occurred (the attempt should be rescheduled).
+     **/
+    @Override
+    public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+        return getDelegate().getAvailableRestoreSets();
+    }
+
+    /**
+     * Get the identifying token of the backup set currently being stored from
+     * this device.  This is used in the case of applications wishing to restore
+     * their last-known-good data.
+     *
+     * @return A token that can be passed to {@link #startRestore}, or 0 if there
+     *   is no backup set available corresponding to the current device state.
+     */
+    @Override
+    public long getCurrentRestoreSet() throws RemoteException {
+        return getDelegate().getCurrentRestoreSet();
+    }
+
+    /**
+     * Start restoring application data from backup.  After calling this function,
+     * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+     * to walk through the actual application data.
+     *
+     * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+     *   or {@link #getCurrentRestoreSet}.
+     * @param packages List of applications to restore (if data is available).
+     *   Application data will be restored in the order given.
+     * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
+     *   {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
+     *   (an error occurred, the restore should be aborted and rescheduled).
+     */
+    @Override
+    public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+        return getDelegate().startRestore(token, packages);
+    }
+
+    /**
+     * Get the package name of the next application with data in the backup store, plus
+     * a description of the structure of the restored archive: either TYPE_KEY_VALUE for
+     * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
+     *
+     * <p>If the package name in the returned RestoreDescription object is the singleton
+     * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
+     * in the current restore session: all packages described in startRestore() have been
+     * processed.
+     *
+     * <p>If this method returns {@code null}, it means that a transport-level error has
+     * occurred and the entire restore operation should be abandoned.
+     *
+     * @return A RestoreDescription object containing the name of one of the packages
+     *   supplied to {@link #startRestore} plus an indicator of the data type of that
+     *   restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
+     *   no more packages can be restored in this session; or {@code null} to indicate
+     *   a transport-level error.
+     */
+    @Override
+    public RestoreDescription nextRestorePackage() throws RemoteException {
+        return getDelegate().nextRestorePackage();
+    }
+
+    /**
+     * Get the data for the application returned by {@link #nextRestorePackage}.
+     *
+     * @param outFd An open, writable file into which the backup data should be stored.
+     * @return the same error codes as {@link #startRestore}.
+     */
+    @Override
+    public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+        return getDelegate().getRestoreData(outFd);
+    }
+
+    /**
+     * End a restore session (aborting any in-process data transfer as necessary),
+     * freeing any resources and connections used during the restore process.
+     */
+    @Override
+    public void finishRestore() throws RemoteException {
+        getDelegate().finishRestore();
+    }
+
+    @Override
+    public long requestFullBackupTime() throws RemoteException {
+        return getDelegate().requestFullBackupTime();
+    }
+
+    @Override
+    public int performFullBackup(PackageInfo targetPackage,
+            ParcelFileDescriptor socket, int flags) throws RemoteException {
+        return getDelegate().performFullBackup(targetPackage, socket, flags);
+    }
+
+    @Override
+    public int checkFullBackupSize(long size) throws RemoteException {
+        return getDelegate().checkFullBackupSize(size);
+    }
+
+    @Override
+    public int sendBackupData(int numBytes) throws RemoteException {
+        return getDelegate().sendBackupData(numBytes);
+    }
+
+    @Override
+    public void cancelFullBackup() throws RemoteException {
+        getDelegate().cancelFullBackup();
+    }
+
+    /**
+     * Ask the transport whether this app is eligible for backup.
+     *
+     * @param targetPackage The identity of the application.
+     * @param isFullBackup If set, transport should check if app is eligible for full data backup,
+     *   otherwise to check if eligible for key-value backup.
+     * @return Whether this app is eligible for backup.
+     */
+    @Override
+    public boolean isAppEligibleForBackup(PackageInfo targetPackage,
+            boolean isFullBackup) throws RemoteException {
+        return getDelegate().isAppEligibleForBackup(targetPackage, isFullBackup);
+    }
+
+    /**
+     * Ask the transport about current quota for backup size of the package.
+     *
+     * @param packageName  ID of package to provide the quota.
+     * @param isFullBackup If set, transport should return limit for full data backup, otherwise
+     *                     for key-value backup.
+     * @return Current limit on full data backup size in bytes.
+     */
+    @Override
+    public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
+        return getDelegate().getBackupQuota(packageName, isFullBackup);
+    }
+
+    /**
+     * Ask the transport to provide data for the "current" package being restored.  This
+     * is the package that was just reported by {@link #nextRestorePackage()} as having
+     * {@link RestoreDescription#TYPE_FULL_STREAM} data.
+     *
+     * The transport writes some data to the socket supplied to this call, and returns
+     * the number of bytes written.  The system will then read that many bytes and
+     * stream them to the application's agent for restore, then will call this method again
+     * to receive the next chunk of the archive.  This sequence will be repeated until the
+     * transport returns zero indicating that all of the package's data has been delivered
+     * (or returns a negative value indicating some sort of hard error condition at the
+     * transport level).
+     *
+     * <p>After this method returns zero, the system will then call
+     * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+     * application, and the sequence begins again.
+     *
+     * <p>The transport should always close this socket when returning from this method.
+     * Do not cache this socket across multiple calls or you may leak file descriptors.
+     *
+     * @param socket The file descriptor that the transport will use for delivering the
+     *    streamed archive.  The transport must close this socket in all cases when returning
+     *    from this method.
+     * @return 0 when no more data for the current package is available.  A positive value
+     *    indicates the presence of that many bytes to be delivered to the app.  Any negative
+     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
+     *    indicating a fatal error condition that precludes further restore operations
+     *    on the current dataset.
+     */
+    @Override
+    public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
+        return getDelegate().getNextFullRestoreDataChunk(socket);
+    }
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    @Override
+    public int abortFullRestore() throws RemoteException {
+        return getDelegate().abortFullRestore();
+    }
+
+    /**
+     * Returns flags with additional information about the transport, which is accessible to the
+     * {@link BackupAgent}. This allows the agent to decide what to backup or
+     * restore based on properties of the transport.
+     *
+     * <p>For supported flags see {@link BackupAgent}.
+     */
+    @Override
+    public int getTransportFlags() throws RemoteException {
+        return getDelegate().getTransportFlags();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportClient.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportClientManager.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStats.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportStats.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportUtils.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java b/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
deleted file mode 100644
index 5bec1a9..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.android.server.backup.encryption.chunk;
-
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-
-/**
- * Information about a chunk entry in a protobuf. Only used for reading from a {@link
- * ProtoInputStream}.
- */
-public class Chunk {
-    /**
-     * Reads a Chunk from a {@link ProtoInputStream}. Expects the message to be of format {@link
-     * ChunksMetadataProto.Chunk}.
-     *
-     * @param inputStream currently at a {@link ChunksMetadataProto.Chunk} message.
-     * @throws IOException when the message is not structured as expected or a field can not be
-     *     read.
-     */
-    static Chunk readFromProto(ProtoInputStream inputStream) throws IOException {
-        Chunk result = new Chunk();
-
-        while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (inputStream.getFieldNumber()) {
-                case (int) ChunksMetadataProto.Chunk.HASH:
-                    result.mHash = inputStream.readBytes(ChunksMetadataProto.Chunk.HASH);
-                    break;
-                case (int) ChunksMetadataProto.Chunk.LENGTH:
-                    result.mLength = inputStream.readInt(ChunksMetadataProto.Chunk.LENGTH);
-                    break;
-            }
-        }
-
-        return result;
-    }
-
-    private int mLength;
-    private byte[] mHash;
-
-    /** Private constructor. This class should only be instantiated by calling readFromProto. */
-    private Chunk() {
-        // Set default values for fields in case they are not available in the proto.
-        mHash = new byte[]{};
-        mLength = 0;
-    }
-
-    public int getLength() {
-        return mLength;
-    }
-
-    public byte[] getHash() {
-        return mHash;
-    }
-}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
deleted file mode 100644
index 1ae598e..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import com.android.internal.util.Preconditions;
-import java.util.Arrays;
-import java.util.Base64;
-
-/**
- * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key.
- *
- * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link
- * #hashCode()}.
- */
-public class ChunkHash implements Comparable<ChunkHash> {
-    /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */
-    public static final int HASH_LENGTH_BYTES = 256 / 8;
-
-    private static final int UNSIGNED_MASK = 0xFF;
-
-    private final byte[] mHash;
-
-    /** Constructs a new instance which wraps the given SHA-256 hash bytes. */
-    public ChunkHash(byte[] hash) {
-        Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits");
-        mHash = hash;
-    }
-
-    public byte[] getHash() {
-        return mHash;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof ChunkHash)) {
-            return false;
-        }
-
-        ChunkHash chunkHash = (ChunkHash) o;
-        return Arrays.equals(mHash, chunkHash.mHash);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mHash);
-    }
-
-    @Override
-    public int compareTo(ChunkHash other) {
-        return lexicographicalCompareUnsignedBytes(getHash(), other.getHash());
-    }
-
-    @Override
-    public String toString() {
-        return Base64.getEncoder().encodeToString(mHash);
-    }
-
-    private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) {
-        int minLength = Math.min(left.length, right.length);
-        for (int i = 0; i < minLength; i++) {
-            int result = toInt(left[i]) - toInt(right[i]);
-            if (result != 0) {
-                return result;
-            }
-        }
-        return left.length - right.length;
-    }
-
-    private static int toInt(byte value) {
-        return value & UNSIGNED_MASK;
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java
deleted file mode 100644
index a448901..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import android.annotation.Nullable;
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Chunk listing in a format optimized for quick look-up of chunks via their hash keys. This is
- * useful when building an incremental backup. After a chunk has been produced, the algorithm can
- * quickly look up whether the chunk existed in the previous backup by checking this chunk listing.
- * It can then tell the server to use that chunk, through telling it the position and length of the
- * chunk in the previous backup's blob.
- */
-public class ChunkListingMap {
-    /**
-     * Reads a ChunkListingMap from a {@link ProtoInputStream}. Expects the message to be of format
-     * {@link ChunksMetadataProto.ChunkListing}.
-     *
-     * @param inputStream Currently at a {@link ChunksMetadataProto.ChunkListing} message.
-     * @throws IOException when the message is not structured as expected or a field can not be
-     *     read.
-     */
-    public static ChunkListingMap readFromProto(ProtoInputStream inputStream) throws IOException {
-        Map<ChunkHash, Entry> entries = new HashMap();
-
-        long start = 0;
-
-        while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            if (inputStream.getFieldNumber() == (int) ChunksMetadataProto.ChunkListing.CHUNKS) {
-                long chunkToken = inputStream.start(ChunksMetadataProto.ChunkListing.CHUNKS);
-                Chunk chunk = Chunk.readFromProto(inputStream);
-                entries.put(new ChunkHash(chunk.getHash()), new Entry(start, chunk.getLength()));
-                start += chunk.getLength();
-                inputStream.end(chunkToken);
-            }
-        }
-
-        return new ChunkListingMap(entries);
-    }
-
-    private final Map<ChunkHash, Entry> mChunksByHash;
-
-    private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
-        mChunksByHash = Collections.unmodifiableMap(new HashMap<>(chunksByHash));
-    }
-
-    /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */
-    public boolean hasChunk(ChunkHash hash) {
-        return mChunksByHash.containsKey(hash);
-    }
-
-    /**
-     * Returns the entry for the chunk with the given hash.
-     *
-     * @param hash The SHA-256 MAC of the plaintext of the chunk.
-     * @return The entry, containing position and length of the chunk in the backup blob, or null if
-     *     it does not exist.
-     */
-    @Nullable
-    public Entry getChunkEntry(ChunkHash hash) {
-        return mChunksByHash.get(hash);
-    }
-
-    /** Returns the number of chunks in this listing. */
-    public int getChunkCount() {
-        return mChunksByHash.size();
-    }
-
-    /** Information about a chunk entry in a backup blob - i.e., its position and length. */
-    public static final class Entry {
-        private final int mLength;
-        private final long mStart;
-
-        private Entry(long start, int length) {
-            mStart = start;
-            mLength = length;
-        }
-
-        /** Returns the length of the chunk in bytes. */
-        public int getLength() {
-            return mLength;
-        }
-
-        /** Returns the start position of the chunk in the backup blob, in bytes. */
-        public long getStart() {
-            return mStart;
-        }
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
deleted file mode 100644
index df36c94..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.EXPLICIT_STARTS;
-import static com.android.server.backup.encryption.chunk.ChunksMetadataProto.INLINE_LENGTHS;
-
-import android.annotation.IntDef;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** IntDef corresponding to the ChunkOrderingType enum in the ChunksMetadataProto protobuf. */
-@IntDef({CHUNK_ORDERING_TYPE_UNSPECIFIED, EXPLICIT_STARTS, INLINE_LENGTHS})
-@Retention(RetentionPolicy.SOURCE)
-public @interface ChunkOrderingType {}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java b/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
deleted file mode 100644
index 3a6d1f6..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import java.util.Arrays;
-
-/**
- * Holds the bytes of an encrypted {@link ChunksMetadataProto.ChunkOrdering}.
- *
- * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
- * encryptedChunkOrdering() to getBytes().
- */
-public class EncryptedChunkOrdering {
-    /**
-     * Constructs a new object holding the given bytes of an encrypted {@link
-     * ChunksMetadataProto.ChunkOrdering}.
-     *
-     * <p>Note that this just holds an ordering which is already encrypted, it does not encrypt the
-     * ordering.
-     */
-    public static EncryptedChunkOrdering create(byte[] encryptedChunkOrdering) {
-        return new EncryptedChunkOrdering(encryptedChunkOrdering);
-    }
-
-    private final byte[] mEncryptedChunkOrdering;
-
-    public byte[] encryptedChunkOrdering() {
-        return mEncryptedChunkOrdering;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof EncryptedChunkOrdering)) {
-            return false;
-        }
-
-        EncryptedChunkOrdering encryptedChunkOrdering = (EncryptedChunkOrdering) o;
-        return Arrays.equals(
-                mEncryptedChunkOrdering, encryptedChunkOrdering.mEncryptedChunkOrdering);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mEncryptedChunkOrdering);
-    }
-
-    private EncryptedChunkOrdering(byte[] encryptedChunkOrdering) {
-        mEncryptedChunkOrdering = encryptedChunkOrdering;
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
deleted file mode 100644
index 68d9d14..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-
-/** Writes backup data either as a diff script or as raw data, determined by the implementation. */
-public interface BackupWriter {
-    /** Writes the given bytes to the output. */
-    void writeBytes(byte[] bytes) throws IOException;
-
-    /**
-     * Writes an existing chunk from the previous backup to the output.
-     *
-     * <p>Note: not all implementations support this method.
-     */
-    void writeChunk(long start, int length) throws IOException;
-
-    /** Returns the number of bytes written, included bytes copied from the old file. */
-    long getBytesWritten();
-
-    /**
-     * Indicates that no more bytes or chunks will be written.
-     *
-     * <p>After calling this, you may not call {@link #writeBytes(byte[])} or {@link
-     * #writeChunk(long, int)}
-     */
-    void flush() throws IOException;
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
deleted file mode 100644
index 812cfbd..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-/** Encrypts chunks of a file using AES/GCM. */
-public class ChunkEncryptor {
-    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
-    private static final int GCM_TAG_LENGTH_BYTES = 16;
-
-    private final SecretKey mSecretKey;
-    private final SecureRandom mSecureRandom;
-
-    /**
-     * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to
-     * generate nonces.
-     */
-    public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) {
-        this.mSecretKey = secretKey;
-        this.mSecureRandom = secureRandom;
-    }
-
-    /**
-     * Transforms {@code plaintext} into an {@link EncryptedChunk}.
-     *
-     * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk.
-     * @param plaintext Bytes to encrypt.
-     * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption.
-     * @throws IllegalBlockSizeException If the input data cannot be encrypted using
-     *     AES/GCM/NoPadding. This should never be the case.
-     */
-    public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext)
-            throws InvalidKeyException, IllegalBlockSizeException {
-        byte[] nonce = generateNonce();
-        Cipher cipher;
-        try {
-            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
-            cipher.init(
-                    Cipher.ENCRYPT_MODE,
-                    mSecretKey,
-                    new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce));
-        } catch (NoSuchAlgorithmException
-                | NoSuchPaddingException
-                | InvalidAlgorithmParameterException e) {
-            // This can not happen - AES/GCM/NoPadding is supported.
-            throw new AssertionError(e);
-        }
-        byte[] encryptedBytes;
-        try {
-            encryptedBytes = cipher.doFinal(plaintext);
-        } catch (BadPaddingException e) {
-            // This can not happen - BadPaddingException can only be thrown in decrypt mode.
-            throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode.");
-        }
-
-        return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes);
-    }
-
-    private byte[] generateNonce() {
-        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
-        mSecureRandom.nextBytes(nonce);
-        return nonce;
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
deleted file mode 100644
index 145b7bf..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-
-/** Computes the SHA-256 HMAC of a chunk of bytes. */
-public class ChunkHasher {
-    private static final String MAC_ALGORITHM = "HmacSHA256";
-
-    private final SecretKey mSecretKey;
-
-    /** Constructs a new hasher which computes the HMAC using the given secret key. */
-    public ChunkHasher(SecretKey secretKey) {
-        this.mSecretKey = secretKey;
-    }
-
-    /** Returns the SHA-256 over the given bytes. */
-    public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException {
-        try {
-            Mac mac = Mac.getInstance(MAC_ALGORITHM);
-            mac.init(mSecretKey);
-            return new ChunkHash(mac.doFinal(plaintext));
-        } catch (NoSuchAlgorithmException e) {
-            // This can not happen - AES/GCM/NoPadding is available as part of the framework.
-            throw new AssertionError(e);
-        }
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
deleted file mode 100644
index b91913e..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.GeneralSecurityException;
-
-/** Splits an input stream into chunks, which are to be encrypted separately. */
-public interface Chunker {
-    /**
-     * Splits the input stream into chunks.
-     *
-     * @param inputStream The input stream.
-     * @param chunkConsumer A function that processes each chunk as it is produced.
-     * @throws IOException If there is a problem reading the input stream.
-     * @throws GeneralSecurityException if the consumer function throws an error.
-     */
-    void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
-            throws IOException, GeneralSecurityException;
-
-    /** Function that consumes chunks. */
-    interface ChunkConsumer {
-        /**
-         * Invoked for each chunk.
-         *
-         * @param chunk Plaintext bytes of chunk.
-         * @throws GeneralSecurityException if there is an issue encrypting the chunk.
-         */
-        void accept(byte[] chunk) throws GeneralSecurityException;
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
deleted file mode 100644
index 1f936eb..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * A chunk of a file encrypted using AES/GCM.
- *
- * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename
- * encryptedBytes(), key() and nonce().
- */
-public class EncryptedChunk {
-    public static final int KEY_LENGTH_BYTES = ChunkHash.HASH_LENGTH_BYTES;
-    public static final int NONCE_LENGTH_BYTES = 12;
-
-    /**
-     * Constructs a new instance with the given key, nonce, and encrypted bytes.
-     *
-     * @param key SHA-256 Hmac of the chunk plaintext.
-     * @param nonce Nonce with which the bytes of the chunk were encrypted.
-     * @param encryptedBytes Encrypted bytes of the chunk.
-     */
-    public static EncryptedChunk create(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
-        Preconditions.checkArgument(
-                nonce.length == NONCE_LENGTH_BYTES, "Nonce does not have the correct length.");
-        return new EncryptedChunk(key, nonce, encryptedBytes);
-    }
-
-    private ChunkHash mKey;
-    private byte[] mNonce;
-    private byte[] mEncryptedBytes;
-
-    private EncryptedChunk(ChunkHash key, byte[] nonce, byte[] encryptedBytes) {
-        mKey = key;
-        mNonce = nonce;
-        mEncryptedBytes = encryptedBytes;
-    }
-
-    /** The SHA-256 Hmac of the plaintext bytes of the chunk. */
-    public ChunkHash key() {
-        return mKey;
-    }
-
-    /** The nonce with which the chunk was encrypted. */
-    public byte[] nonce() {
-        return mNonce;
-    }
-
-    /** The encrypted bytes of the chunk. */
-    public byte[] encryptedBytes() {
-        return mEncryptedBytes;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof EncryptedChunk)) {
-            return false;
-        }
-
-        EncryptedChunk encryptedChunkOrdering = (EncryptedChunk) o;
-        return Arrays.equals(mEncryptedBytes, encryptedChunkOrdering.mEncryptedBytes)
-                && Arrays.equals(mNonce, encryptedChunkOrdering.mNonce)
-                && mKey.equals(encryptedChunkOrdering.mKey);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey, Arrays.hashCode(mNonce), Arrays.hashCode(mEncryptedBytes));
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
deleted file mode 100644
index eaf701c..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import java.io.IOException;
-
-/** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */
-public interface EncryptedChunkEncoder {
-    /**
-     * Encodes the given chunk and asks the writer to write it.
-     *
-     * <p>The chunk will be encoded in the format [nonce]+[encrypted data].
-     *
-     * <p>TODO(b/116575321): Choose a more descriptive method name after the code move is done.
-     */
-    void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException;
-
-    /**
-     * Returns the length in bytes that this chunk would be if encoded with {@link
-     * #writeChunkToWriter}.
-     */
-    int getEncodedLengthOfChunk(EncryptedChunk chunk);
-
-    /**
-     * Returns the {@link ChunkOrderingType} that must be included in the backup file, when using
-     * this decoder, so that the file may be correctly decoded.
-     */
-    @ChunkOrderingType
-    int getChunkOrderingType();
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
deleted file mode 100644
index 5c902ca..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-import java.io.IOException;
-
-/**
- * Encodes an {@link EncryptedChunk} as bytes, prepending the length of the chunk.
- *
- * <p>This allows us to decode the backup file during restore without any extra information about
- * the boundaries of the chunks. The backup file should contain a chunk ordering in mode {@link
- * ChunksMetadataProto#INLINE_LENGTHS}.
- *
- * <p>We use this implementation during key value backup.
- */
-public class InlineLengthsEncryptedChunkEncoder implements EncryptedChunkEncoder {
-    public static final int BYTES_LENGTH = Integer.SIZE / Byte.SIZE;
-
-    private final LengthlessEncryptedChunkEncoder mLengthlessEncryptedChunkEncoder =
-            new LengthlessEncryptedChunkEncoder();
-
-    @Override
-    public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
-        int length = mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
-        writer.writeBytes(toByteArray(length));
-        mLengthlessEncryptedChunkEncoder.writeChunkToWriter(writer, chunk);
-    }
-
-    @Override
-    public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
-        return BYTES_LENGTH + mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk);
-    }
-
-    @Override
-    @ChunkOrderingType
-    public int getChunkOrderingType() {
-        return ChunksMetadataProto.INLINE_LENGTHS;
-    }
-
-    /**
-     * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to
-     * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code
-     * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}.
-     *
-     * <p>Equivalent to guava's Ints.toByteArray.
-     */
-    static byte[] toByteArray(int value) {
-        return new byte[] {
-            (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value
-        };
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
deleted file mode 100644
index 4b84981..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-import java.io.IOException;
-
-/**
- * Encodes an {@link EncryptedChunk} as bytes without including any information about the length of
- * the chunk.
- *
- * <p>In order for us to decode the backup file during restore it must include a chunk ordering in
- * mode {@link ChunksMetadataProto#EXPLICIT_STARTS}, which contains the boundaries of the chunks in
- * the encrypted file. This information allows us to decode the backup file and divide it into
- * chunks without including the length of each chunk inline.
- *
- * <p>We use this implementation during full backup.
- */
-public class LengthlessEncryptedChunkEncoder implements EncryptedChunkEncoder {
-    @Override
-    public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException {
-        writer.writeBytes(chunk.nonce());
-        writer.writeBytes(chunk.encryptedBytes());
-    }
-
-    @Override
-    public int getEncodedLengthOfChunk(EncryptedChunk chunk) {
-        return chunk.nonce().length + chunk.encryptedBytes().length;
-    }
-
-    @Override
-    @ChunkOrderingType
-    public int getChunkOrderingType() {
-        return ChunksMetadataProto.EXPLICIT_STARTS;
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
deleted file mode 100644
index 839dc7c..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/** Writes data straight to an output stream. */
-public class RawBackupWriter implements BackupWriter {
-    private final OutputStream outputStream;
-    private long bytesWritten;
-
-    /** Constructs a new writer which writes bytes to the given output stream. */
-    public RawBackupWriter(OutputStream outputStream) {
-        this.outputStream = outputStream;
-    }
-
-    @Override
-    public void writeBytes(byte[] bytes) throws IOException {
-        outputStream.write(bytes);
-        bytesWritten += bytes.length;
-    }
-
-    @Override
-    public void writeChunk(long start, int length) throws IOException {
-        throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks");
-    }
-
-    @Override
-    public long getBytesWritten() {
-        return bytesWritten;
-    }
-
-    @Override
-    public void flush() throws IOException {
-        outputStream.flush();
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java b/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
deleted file mode 100644
index ec90f6c..0000000
--- a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.keys;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Locale;
-
-/**
- * Tracks when a tertiary key rotation is due.
- *
- * <p>After a certain number of incremental backups, the device schedules a full backup, which will
- * generate a new encryption key, effecting a key rotation. We should do this on a regular basis so
- * that if a key does become compromised it has limited value to the attacker.
- *
- * <p>No additional synchronization of this class is provided. Only one instance should be used at
- * any time. This should be fine as there should be no parallelism in backups.
- */
-public class TertiaryKeyRotationTracker {
-    private static final int MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION = 31;
-    private static final String SHARED_PREFERENCES_NAME = "tertiary_key_rotation_tracker";
-
-    private static final String TAG = "TertiaryKeyRotationTracker";
-    private static final boolean DEBUG = false;
-
-    /**
-     * A new instance, using {@code context} to commit data to disk via {@link SharedPreferences}.
-     */
-    public static TertiaryKeyRotationTracker getInstance(Context context) {
-        return new TertiaryKeyRotationTracker(
-                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE));
-    }
-
-    private final SharedPreferences mSharedPreferences;
-
-    /** New instance, storing data in {@code mSharedPreferences}. */
-    @VisibleForTesting
-    TertiaryKeyRotationTracker(SharedPreferences sharedPreferences) {
-        mSharedPreferences = sharedPreferences;
-    }
-
-    /**
-     * Returns {@code true} if the given app is due having its key rotated.
-     *
-     * @param packageName The package name of the app.
-     */
-    public boolean isKeyRotationDue(String packageName) {
-        return getBackupsSinceRotation(packageName) >= MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION;
-    }
-
-    /**
-     * Records that an incremental backup has occurred. Each incremental backup brings the app
-     * closer to the time when its key should be rotated.
-     *
-     * @param packageName The package name of the app for which the backup occurred.
-     */
-    public void recordBackup(String packageName) {
-        int backupsSinceRotation = getBackupsSinceRotation(packageName) + 1;
-        mSharedPreferences.edit().putInt(packageName, backupsSinceRotation).apply();
-        if (DEBUG) {
-            Slog.d(
-                    TAG,
-                    String.format(
-                            Locale.US,
-                            "Incremental backup for %s. %d backups until key rotation.",
-                            packageName,
-                            Math.max(
-                                    0,
-                                    MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION
-                                            - backupsSinceRotation)));
-        }
-    }
-
-    /**
-     * Resets the rotation delay for the given app. Should be invoked after a key rotation.
-     *
-     * @param packageName Package name of the app whose key has rotated.
-     */
-    public void resetCountdown(String packageName) {
-        mSharedPreferences.edit().putInt(packageName, 0).apply();
-    }
-
-    /** Marks all enrolled packages for key rotation. */
-    public void markAllForRotation() {
-        SharedPreferences.Editor editor = mSharedPreferences.edit();
-        for (String packageName : mSharedPreferences.getAll().keySet()) {
-            editor.putInt(packageName, MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION);
-        }
-        editor.apply();
-    }
-
-    private int getBackupsSinceRotation(String packageName) {
-        return mSharedPreferences.getInt(packageName, 0);
-    }
-}
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
deleted file mode 100644
index e3df3c1..0000000
--- a/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.tasks;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-
-/**
- * Accepts the plaintext bytes of decrypted chunks and writes them to some output. Also keeps track
- * of the message digest of the chunks.
- */
-public interface DecryptedChunkOutput extends Closeable {
-    /**
-     * Opens whatever output the implementation chooses, ready to process chunks.
-     *
-     * @return {@code this}, to allow use with try-with-resources
-     */
-    DecryptedChunkOutput open() throws IOException;
-
-    /**
-     * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also
-     * updates the digest with the chunk.
-     *
-     * <p>You must call {@link #open()} before this method, and you may not call it after calling
-     * {@link Closeable#close()}.
-     *
-     * @param plaintextBuffer An array containing the bytes of the plaintext of the chunk, starting
-     *     at index 0.
-     * @param length The length in bytes of the plaintext contained in {@code plaintextBuffer}.
-     */
-    void processChunk(byte[] plaintextBuffer, int length) throws IOException, InvalidKeyException;
-
-    /**
-     * Returns the message digest of all the chunks processed by {@link #processChunk}.
-     *
-     * <p>You must call {@link Closeable#close()} before calling this method.
-     */
-    byte[] getDigest();
-}
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 ec2d545..01b40fb 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -110,7 +110,7 @@
             if (MORE_DEBUG) {
                 Slog.v(TAG, "Done consuming input tarfile.");
             }
-        } catch (IOException e) {
+        } catch (Exception e) {
             Slog.e(TAG, "Unable to read restore input");
         } finally {
             try {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 5c6258f..c8dbb36 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -147,8 +147,7 @@
             mRequestsHistory = null;
         }
 
-        final UserManager um = getContext().getSystemService(UserManager.class);
-        final List<UserInfo> users = um.getUsers();
+        final List<UserInfo> users = getSupportedUsers();
         for (int i = 0; i < users.size(); i++) {
             final int userId = users.get(i).id;
             final boolean disabled = !isEnabledBySettings(userId);
@@ -174,6 +173,11 @@
     }
 
     @Override // from SystemService
+    public boolean isSupported(UserInfo userInfo) {
+        return userInfo.isFull() || userInfo.isManagedProfile();
+    }
+
+    @Override // from SystemService
     public void onStart() {
         publishBinderService(CONTENT_CAPTURE_MANAGER_SERVICE,
                 new ContentCaptureManagerServiceStub());
@@ -336,8 +340,7 @@
         if (verbose) {
             Slog.v(mTag, "setDisabledByDeviceConfig(): explicitlyEnabled=" + explicitlyEnabled);
         }
-        final UserManager um = getContext().getSystemService(UserManager.class);
-        final List<UserInfo> users = um.getUsers();
+        final List<UserInfo> users = getSupportedUsers();
 
         final boolean newDisabledValue;
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1643221..80bc1af 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -1,3 +1,60 @@
+java_library {
+    name: "protolog-common",
+    srcs: [
+        "java/com/android/server/protolog/common/**/*.java",
+    ],
+    host_supported: true,
+}
+
+java_library {
+    name: "services.core.wm.protologgroups",
+    srcs: [
+        "java/com/android/server/wm/ProtoLogGroup.java",
+    ],
+    static_libs: ["protolog-common"],
+}
+
+genrule {
+    name: "services.core.protologsrc",
+    srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) transform-protolog-calls " +
+      "--protolog-class com.android.server.protolog.common.ProtoLog " +
+      "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
+      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--output-srcjar $(out) " +
+      "$(locations java/**/*.java)",
+    out: ["services.core.protolog.srcjar"],
+}
+
+genrule {
+    name: "generate-protolog.json",
+    srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+    tools: ["protologtool"],
+    cmd: "$(location protologtool) generate-viewer-config " +
+      "--protolog-class com.android.server.protolog.common.ProtoLog " +
+      "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+      "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+      "--viewer-conf $(out) " +
+      "$(locations java/**/*.java)",
+    out: ["services.core.protolog.json"],
+}
+
+genrule {
+    name: "checked-protolog.json",
+    srcs: [
+        ":generate-protolog.json",
+        ":services.core.protolog.json",
+    ],
+    cmd: "cp $(location :generate-protolog.json) $(out) && " +
+      "{ diff $(out) $(location :services.core.protolog.json) >/dev/null 2>&1 || " +
+      "{ echo -e '##### ProtoLog viewer config is stale. ### \nRun: \n " +
+      "cp $(location :generate-protolog.json) " +
+      "$(location :services.core.protolog.json)\n' >&2 && false; } }",
+    out: ["services.core.protolog.json"],
+}
+
 java_library_static {
     name: "services.core.unboosted",
 
@@ -12,7 +69,7 @@
         ],
     },
     srcs: [
-        "java/**/*.java",
+        ":services.core.protologsrc",
         ":dumpstate_aidl",
         ":idmap2_aidl",
         ":installd_aidl",
@@ -34,6 +91,7 @@
 
     required: [
         "gps_debug.conf",
+        "protolog.conf.json.gz",
     ],
 
     static_libs: [
@@ -81,3 +139,15 @@
     name: "gps_debug.conf",
     src: "java/com/android/server/location/gps_debug.conf",
 }
+
+genrule {
+    name: "services.core.json.gz",
+    srcs: [":checked-protolog.json"],
+    out: ["services.core.protolog.json.gz"],
+    cmd: "gzip < $(in) > $(out)",
+}
+
+prebuilt_etc {
+    name: "protolog.conf.json.gz",
+    src: ":services.core.json.gz",
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e67ccc4..18a8148 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1102,7 +1102,7 @@
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
 
-        final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext);
+        final DataConnectionStats dataConnectionStats = new DataConnectionStats(mContext, mHandler);
         dataConnectionStats.startMonitoring();
 
         mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
@@ -2165,7 +2165,11 @@
         }
     }
 
-    void systemReady() {
+    /**
+     * Called when the system is ready and ConnectivityService can initialize remaining components.
+     */
+    @VisibleForTesting
+    public void systemReady() {
         mProxyTracker.loadGlobalProxy();
         registerNetdEventCallback();
         mTethering.systemReady();
@@ -4551,7 +4555,7 @@
                     Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
                     return false;
                 }
-                setLockdownTracker(new LockdownVpnTracker(mContext, mNMS, this, vpn, profile));
+                setLockdownTracker(new LockdownVpnTracker(mContext, this, mHandler, vpn, profile));
             } else {
                 setLockdownTracker(null);
             }
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 5089ee0..69f226f6 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -21,9 +21,7 @@
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
-import static android.location.LocationProvider.AVAILABLE;
 import static android.os.PowerManager.locationPowerSaveModeToString;
-import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
@@ -1088,34 +1086,6 @@
             pw.decreaseIndent();
         }
 
-        @GuardedBy("mLock")
-        public long getStatusUpdateTimeLocked() {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    return mProvider.getStatusUpdateTime();
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            } else {
-                return 0;
-            }
-        }
-
-        @GuardedBy("mLock")
-        public int getStatusLocked(Bundle extras) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    return mProvider.getStatus(extras);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            } else {
-                return AVAILABLE;
-            }
-        }
-
         @Override
         public void onReportLocation(Location location) {
             synchronized (mLock) {
@@ -1324,18 +1294,6 @@
             super.setRequest(request, workSource);
             mCurrentRequest = request;
         }
-
-        @GuardedBy("mLock")
-        public void setStatusLocked(int status, Bundle extras, long updateTime) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    ((MockProvider) mProvider).setStatus(status, extras, updateTime);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
     }
 
     /**
@@ -1516,34 +1474,6 @@
             throw new IllegalStateException("Request for non-existent listener");
         }
 
-        public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
-            if (mListener != null) {
-                try {
-                    mListener.onStatusChanged(provider, status, extras);
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (RemoteException e) {
-                    return false;
-                }
-            } else {
-                Intent statusChanged = new Intent();
-                statusChanged.putExtras(new Bundle(extras));
-                statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
-                try {
-                    mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
-                            getResolutionPermission(mAllowedResolutionLevel),
-                            PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (PendingIntent.CanceledException e) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
         public boolean callLocationChangedLocked(Location location) {
             if (mListener != null) {
                 try {
@@ -2296,7 +2226,6 @@
         private final Receiver mReceiver;
         private boolean mIsForegroundUid;
         private Location mLastFixBroadcast;
-        private long mLastStatusBroadcast;
         private Throwable mStackTrace;  // for debugging only
 
         /**
@@ -3406,26 +3335,6 @@
                 }
             }
 
-            // TODO: location provider status callbacks have been disabled and deprecated, and are
-            // guarded behind this setting now. should be removed completely post-Q
-            if (Settings.Global.getInt(mContext.getContentResolver(),
-                    LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
-                long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
-                Bundle extras = new Bundle();
-                int status = provider.getStatusLocked(extras);
-
-                long prevStatusUpdateTime = r.mLastStatusBroadcast;
-                if ((newStatusUpdateTime > prevStatusUpdateTime)
-                        && (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
-
-                    r.mLastStatusBroadcast = newStatusUpdateTime;
-                    if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
-                        receiverDead = true;
-                        Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
-                    }
-                }
-            }
-
             // track expired records
             if (r.mRealRequest.getNumUpdates() <= 0 || r.mRealRequest.getExpireAt() < now) {
                 if (deadUpdateRecords == null) {
@@ -3625,23 +3534,6 @@
     }
 
     @Override
-    public void setTestProviderStatus(String providerName, int status, Bundle extras,
-            long updateTime, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
-        }
-
-        synchronized (mLock) {
-            LocationProvider testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
-        }
-    }
-
-    @Override
     @NonNull
     public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
             String opPackageName) {
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index 1891ba9..6bc1a57 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.util.Slog;
+import java.util.concurrent.TimeUnit;
 
 public class MountServiceIdler extends JobService {
     private static final String TAG = "MountServiceIdler";
@@ -48,7 +49,7 @@
                     mStarted = false;
                 }
             }
-            // ... and try again tomorrow
+            // ... and try again right away or tomorrow
             scheduleIdlePass(MountServiceIdler.this);
         }
     };
@@ -98,24 +99,32 @@
     public static void scheduleIdlePass(Context context) {
         JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
 
-        Calendar calendar = tomorrowMidnight();
-        final long timeToMidnight = calendar.getTimeInMillis() - System.currentTimeMillis();
+        final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis();
+        final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis();
+        final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis();
+
+        long nextScheduleTime;
+        if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) {
+            nextScheduleTime = TimeUnit.SECONDS.toMillis(10);
+        } else {
+            nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow
+        }
 
         JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
         builder.setRequiresDeviceIdle(true);
-        builder.setRequiresCharging(true);
-        builder.setMinimumLatency(timeToMidnight);
+        builder.setRequiresBatteryNotLow(true);
+        builder.setMinimumLatency(nextScheduleTime);
         tm.schedule(builder.build());
     }
 
-    private static Calendar tomorrowMidnight() {
+    private static Calendar offsetFromTodayMidnight(int nDays, int nHours) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(System.currentTimeMillis());
-        calendar.set(Calendar.HOUR_OF_DAY, 3);
+        calendar.set(Calendar.HOUR_OF_DAY, nHours);
         calendar.set(Calendar.MINUTE, 0);
         calendar.set(Calendar.SECOND, 0);
         calendar.set(Calendar.MILLISECOND, 0);
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.add(Calendar.DAY_OF_MONTH, nDays);
         return calendar;
     }
 }
diff --git a/services/core/java/com/android/server/NetIdManager.java b/services/core/java/com/android/server/NetIdManager.java
index 11533be..097fb3a 100644
--- a/services/core/java/com/android/server/NetIdManager.java
+++ b/services/core/java/com/android/server/NetIdManager.java
@@ -20,6 +20,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Class used to reserve and release net IDs.
@@ -38,14 +39,25 @@
     @GuardedBy("mNetIdInUse")
     private int mLastNetId = MIN_NET_ID - 1;
 
+    private final int mMaxNetId;
+
+    public NetIdManager() {
+        this(MAX_NET_ID);
+    }
+
+    @VisibleForTesting
+    NetIdManager(int maxNetId) {
+        mMaxNetId = maxNetId;
+    }
+
     /**
      * Get the first netId that follows the provided lastId and is available.
      */
-    private static int getNextAvailableNetIdLocked(
+    private int getNextAvailableNetIdLocked(
             int lastId, @NonNull SparseBooleanArray netIdInUse) {
         int netId = lastId;
-        for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
-            netId = netId < MAX_NET_ID ? netId + 1 : MIN_NET_ID;
+        for (int i = MIN_NET_ID; i <= mMaxNetId; i++) {
+            netId = netId < mMaxNetId ? netId + 1 : MIN_NET_ID;
             if (!netIdInUse.get(netId)) {
                 return netId;
             }
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 73ec561..bc50956 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,12 +29,12 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -81,10 +81,14 @@
             "watchdog_explicit_health_check_enabled";
 
     // Duration to count package failures before it resets to 0
-    private static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
+    @VisibleForTesting
+    static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
             (int) TimeUnit.MINUTES.toMillis(1);
     // Number of package failures within the duration above before we notify observers
-    private static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+    @VisibleForTesting
+    static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+    @VisibleForTesting
+    static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
     // Whether explicit health checks are enabled or not
     private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
 
@@ -117,6 +121,10 @@
     private final AtomicFile mPolicyFile;
     private final ExplicitHealthCheckController mHealthCheckController;
     private final ConnectivityModuleConnector mConnectivityModuleConnector;
+    private final Runnable mSyncRequests = this::syncRequests;
+    private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
+    private final Runnable mSaveToFile = this::saveToFile;
+    private final SystemClock mSystemClock;
     @GuardedBy("mLock")
     private boolean mIsPackagesReady;
     // Flag to control whether explicit health checks are supported or not
@@ -131,9 +139,11 @@
     @GuardedBy("mLock")
     private long mUptimeAtLastStateSync;
 
-    private final Runnable mSyncRequests = this::syncRequests;
-    private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
-    private final Runnable mSaveToFile = this::saveToFile;
+    @FunctionalInterface
+    @VisibleForTesting
+    interface SystemClock {
+        long uptimeMillis();
+    }
 
     private PackageWatchdog(Context context) {
         // Needs to be constructed inline
@@ -142,7 +152,8 @@
                                 "package-watchdog.xml")),
                 new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
                 new ExplicitHealthCheckController(context),
-                ConnectivityModuleConnector.getInstance());
+                ConnectivityModuleConnector.getInstance(),
+                android.os.SystemClock::uptimeMillis);
     }
 
     /**
@@ -151,13 +162,14 @@
     @VisibleForTesting
     PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
             Handler longTaskHandler, ExplicitHealthCheckController controller,
-            ConnectivityModuleConnector connectivityModuleConnector) {
+            ConnectivityModuleConnector connectivityModuleConnector, SystemClock clock) {
         mContext = context;
         mPolicyFile = policyFile;
         mShortTaskHandler = shortTaskHandler;
         mLongTaskHandler = longTaskHandler;
         mHealthCheckController = controller;
         mConnectivityModuleConnector = connectivityModuleConnector;
+        mSystemClock = clock;
         loadFromFile();
     }
 
@@ -197,7 +209,7 @@
         synchronized (mLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getName());
             if (internalObserver != null) {
-                internalObserver.mRegisteredObserver = observer;
+                internalObserver.registeredObserver = observer;
             }
         }
     }
@@ -215,8 +227,10 @@
      * check state will be reset to a default depending on if the package is contained in
      * {@link mPackagesWithExplicitHealthCheckEnabled}.
      *
-     * @throws IllegalArgumentException if {@code packageNames} is empty
-     * or {@code durationMs} is less than 1
+     * <p>If {@code packageNames} is empty, this will be a no-op.
+     *
+     * <p>If {@code durationMs} is less than 1, a default monitoring duration
+     * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
      */
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
@@ -225,9 +239,9 @@
             return;
         }
         if (durationMs < 1) {
-            // TODO: Instead of failing, monitor for default? 48hrs?
-            throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer "
+            Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
                     + observer.getName() + ". Not observing packages " + packageNames);
+            durationMs = DEFAULT_OBSERVING_DURATION_MS;
         }
 
         List<MonitoredPackage> packages = new ArrayList<>();
@@ -277,28 +291,6 @@
     }
 
     /**
-     * Returns packages observed by {@code observer}
-     *
-     * @return an empty set if {@code observer} has some packages observerd from a previous boot
-     * but has not registered itself in the current boot to receive notifications. Returns null
-     * if there are no active packages monitored from any boot.
-     */
-    @Nullable
-    public Set<String> getPackages(PackageHealthObserver observer) {
-        synchronized (mLock) {
-            for (int i = 0; i < mAllObservers.size(); i++) {
-                if (observer.getName().equals(mAllObservers.keyAt(i))) {
-                    if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
-                        return mAllObservers.valueAt(i).mPackages.keySet();
-                    }
-                    return Collections.emptySet();
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Called when a process fails either due to a crash or ANR.
      *
      * <p>For each package contained in the process, one registered observer with the least user
@@ -322,7 +314,7 @@
                     // Find observer with least user impact
                     for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                         ObserverInternal observer = mAllObservers.valueAt(oIndex);
-                        PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
+                        PackageHealthObserver registeredObserver = observer.registeredObserver;
                         if (registeredObserver != null
                                 && observer.onPackageFailureLocked(
                                         versionedPackage.getPackageName())) {
@@ -473,7 +465,7 @@
         synchronized (mLock) {
             for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
                 ObserverInternal observer = mAllObservers.valueAt(observerIdx);
-                MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
+                MonitoredPackage monitoredPackage = observer.packages.get(packageName);
 
                 if (monitoredPackage != null) {
                     int oldState = monitoredPackage.getHealthCheckStateLocked();
@@ -502,7 +494,7 @@
             Slog.d(TAG, "Received supported packages " + supportedPackages);
             Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
             while (oit.hasNext()) {
-                Iterator<MonitoredPackage> pit = oit.next().mPackages.values().iterator();
+                Iterator<MonitoredPackage> pit = oit.next().packages.values().iterator();
                 while (pit.hasNext()) {
                     MonitoredPackage monitoredPackage = pit.next();
                     String packageName = monitoredPackage.getName();
@@ -535,7 +527,7 @@
         while (oit.hasNext()) {
             ObserverInternal observer = oit.next();
             Iterator<MonitoredPackage> pit =
-                    observer.mPackages.values().iterator();
+                    observer.packages.values().iterator();
             while (pit.hasNext()) {
                 MonitoredPackage monitoredPackage = pit.next();
                 String packageName = monitoredPackage.getName();
@@ -579,7 +571,7 @@
             mUptimeAtLastStateSync = 0;
         } else {
             Slog.i(TAG, "Scheduling next state sync in " + durationMs + "ms");
-            mUptimeAtLastStateSync = SystemClock.uptimeMillis();
+            mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
             mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
         }
     }
@@ -593,7 +585,7 @@
     private long getNextStateSyncMillisLocked() {
         long shortestDurationMs = Long.MAX_VALUE;
         for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-            ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
+            ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).packages;
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                 MonitoredPackage mp = packages.valueAt(pIndex);
                 long duration = mp.getShortestScheduleDurationMsLocked();
@@ -612,7 +604,7 @@
     @GuardedBy("mLock")
     private void pruneObserversLocked() {
         long elapsedMs = mUptimeAtLastStateSync == 0
-                ? 0 : SystemClock.uptimeMillis() - mUptimeAtLastStateSync;
+                ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
         if (elapsedMs <= 0) {
             Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
             return;
@@ -627,8 +619,8 @@
             if (!failedPackages.isEmpty()) {
                 onHealthCheckFailed(observer, failedPackages);
             }
-            if (observer.mPackages.isEmpty()) {
-                Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
+            if (observer.packages.isEmpty()) {
+                Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
                 it.remove();
             }
         }
@@ -638,7 +630,7 @@
             Set<MonitoredPackage> failedPackages) {
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
-                PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
+                PackageHealthObserver registeredObserver = observer.registeredObserver;
                 if (registeredObserver != null) {
                     Iterator<MonitoredPackage> it = failedPackages.iterator();
                     while (it.hasNext()) {
@@ -692,7 +684,7 @@
             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                 ObserverInternal observer = ObserverInternal.read(parser, this);
                 if (observer != null) {
-                    mAllObservers.put(observer.mName, observer);
+                    mAllObservers.put(observer.name, observer);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -737,7 +729,7 @@
                     PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
                     DEFAULT_TRIGGER_FAILURE_DURATION_MS);
             if (mTriggerFailureDurationMs <= 0) {
-                mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_COUNT;
+                mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
             }
 
             setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
@@ -818,18 +810,16 @@
      * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
      * instances of this class.
      */
-    //TODO(b/120598832): Remove 'm' from non-private fields
     private static class ObserverInternal {
-        public final String mName;
-        //TODO(b/120598832): Add getter for mPackages
+        public final String name;
         @GuardedBy("mLock")
-        public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
+        public final ArrayMap<String, MonitoredPackage> packages = new ArrayMap<>();
         @Nullable
         @GuardedBy("mLock")
-        public PackageHealthObserver mRegisteredObserver;
+        public PackageHealthObserver registeredObserver;
 
         ObserverInternal(String name, List<MonitoredPackage> packages) {
-            mName = name;
+            this.name = name;
             updatePackagesLocked(packages);
         }
 
@@ -841,9 +831,9 @@
         public boolean writeLocked(XmlSerializer out) {
             try {
                 out.startTag(null, TAG_OBSERVER);
-                out.attribute(null, ATTR_NAME, mName);
-                for (int i = 0; i < mPackages.size(); i++) {
-                    MonitoredPackage p = mPackages.valueAt(i);
+                out.attribute(null, ATTR_NAME, name);
+                for (int i = 0; i < packages.size(); i++) {
+                    MonitoredPackage p = packages.valueAt(i);
                     p.writeLocked(out);
                 }
                 out.endTag(null, TAG_OBSERVER);
@@ -858,7 +848,7 @@
         public void updatePackagesLocked(List<MonitoredPackage> packages) {
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                 MonitoredPackage p = packages.get(pIndex);
-                mPackages.put(p.mName, p);
+                this.packages.put(p.mName, p);
             }
         }
 
@@ -875,13 +865,13 @@
         @GuardedBy("mLock")
         private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
             Set<MonitoredPackage> failedPackages = new ArraySet<>();
-            Iterator<MonitoredPackage> it = mPackages.values().iterator();
+            Iterator<MonitoredPackage> it = packages.values().iterator();
             while (it.hasNext()) {
                 MonitoredPackage p = it.next();
                 int oldState = p.getHealthCheckStateLocked();
                 int newState = p.handleElapsedTimeLocked(elapsedMs);
-                if (oldState != MonitoredPackage.STATE_FAILED
-                        && newState == MonitoredPackage.STATE_FAILED) {
+                if (oldState != HealthCheckState.FAILED
+                        && newState == HealthCheckState.FAILED) {
                     Slog.i(TAG, "Package " + p.mName + " failed health check");
                     failedPackages.add(p);
                 }
@@ -898,7 +888,7 @@
          */
         @GuardedBy("mLock")
         public boolean onPackageFailureLocked(String packageName) {
-            MonitoredPackage p = mPackages.get(packageName);
+            MonitoredPackage p = packages.get(packageName);
             if (p != null) {
                 return p.onFailureLocked();
             }
@@ -956,6 +946,23 @@
         }
     }
 
+    @Retention(SOURCE)
+    @IntDef(value = {
+            HealthCheckState.ACTIVE,
+            HealthCheckState.INACTIVE,
+            HealthCheckState.PASSED,
+            HealthCheckState.FAILED})
+    public @interface HealthCheckState {
+        // The package has not passed health check but has requested a health check
+        int ACTIVE = 0;
+        // The package has not passed health check and has not requested a health check
+        int INACTIVE = 1;
+        // The package has passed health check
+        int PASSED = 2;
+        // The package has failed health check
+        int FAILED = 3;
+    }
+
     /**
      * Represents a package and its health check state along with the time
      * it should be monitored for.
@@ -964,23 +971,15 @@
      * instances of this class.
      */
     class MonitoredPackage {
-        // Health check states
-        // TODO(b/120598832): Prefix with HEALTH_CHECK
-        // mName has not passed health check but has requested a health check
-        public static final int STATE_ACTIVE = 0;
-        // mName has not passed health check and has not requested a health check
-        public static final int STATE_INACTIVE = 1;
-        // mName has passed health check
-        public static final int STATE_PASSED = 2;
-        // mName has failed health check
-        public static final int STATE_FAILED = 3;
-
         //TODO(b/120598832): VersionedPackage?
         private final String mName;
+        // Times when package failures happen sorted in ascending order
+        @GuardedBy("mLock")
+        private final LongArrayQueue mFailureHistory = new LongArrayQueue();
         // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
         // methods that could change the health check state: handleElapsedTimeLocked and
         // tryPassHealthCheckLocked
-        private int mHealthCheckState = STATE_INACTIVE;
+        private int mHealthCheckState = HealthCheckState.INACTIVE;
         // Whether an explicit health check has passed.
         // This value in addition with mHealthCheckDurationMs determines the health check state
         // of the package, see #getHealthCheckStateLocked
@@ -996,12 +995,6 @@
         // of the package, see #getHealthCheckStateLocked
         @GuardedBy("mLock")
         private long mHealthCheckDurationMs = Long.MAX_VALUE;
-        // System uptime of first package failure
-        @GuardedBy("mLock")
-        private long mUptimeStartMs;
-        // Number of failures since mUptimeStartMs
-        @GuardedBy("mLock")
-        private int mFailures;
 
         MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
             this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
@@ -1036,20 +1029,17 @@
          */
         @GuardedBy("mLock")
         public boolean onFailureLocked() {
-            final long now = SystemClock.uptimeMillis();
-            final long duration = now - mUptimeStartMs;
-            if (duration > mTriggerFailureDurationMs) {
-                // TODO(b/120598832): Reseting to 1 is not correct
-                // because there may be more than 1 failure in the last trigger window from now
-                // This is the RescueParty impl, will leave for now
-                mFailures = 1;
-                mUptimeStartMs = now;
-            } else {
-                mFailures++;
+            // Sliding window algorithm: find out if there exists a window containing failures >=
+            // mTriggerFailureCount.
+            final long now = mSystemClock.uptimeMillis();
+            mFailureHistory.addLast(now);
+            while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
+                // Prune values falling out of the window
+                mFailureHistory.removeFirst();
             }
-            boolean failed = mFailures >= mTriggerFailureCount;
+            boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
             if (failed) {
-                mFailures = 0;
+                mFailureHistory.clear();
             }
             return failed;
         }
@@ -1067,7 +1057,7 @@
                         + ". Using total duration " + mDurationMs + "ms instead");
                 initialHealthCheckDurationMs = mDurationMs;
             }
-            if (mHealthCheckState == STATE_INACTIVE) {
+            if (mHealthCheckState == HealthCheckState.INACTIVE) {
                 // Transitions to ACTIVE
                 mHealthCheckDurationMs = initialHealthCheckDurationMs;
             }
@@ -1087,7 +1077,7 @@
             }
             // Transitions to FAILED if now <= 0 and health check not passed
             mDurationMs -= elapsedMs;
-            if (mHealthCheckState == STATE_ACTIVE) {
+            if (mHealthCheckState == HealthCheckState.ACTIVE) {
                 // We only update health check durations if we have #setHealthCheckActiveLocked
                 // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
                 // Transitions to FAILED if now <= 0 and health check not passed
@@ -1097,14 +1087,15 @@
         }
 
         /**
-         * Marks the health check as passed and transitions to {@link #STATE_PASSED}
-         * if not yet {@link #STATE_FAILED}.
+         * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
+         * if not yet {@link HealthCheckState.FAILED}.
          *
-         * @return the new health check state
+         * @return the new {@link HealthCheckState health check state}
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         public int tryPassHealthCheckLocked() {
-            if (mHealthCheckState != STATE_FAILED) {
+            if (mHealthCheckState != HealthCheckState.FAILED) {
                 // FAILED is a final state so only pass if we haven't failed
                 // Transition to PASSED
                 mHasPassedHealthCheck = true;
@@ -1117,12 +1108,11 @@
             return mName;
         }
 
-        //TODO(b/120598832): IntDef
         /**
-         * Returns the current health check state, any of {@link #STATE_ACTIVE},
-         * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
+         * Returns the current {@link HealthCheckState health check state}.
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         public int getHealthCheckStateLocked() {
             return mHealthCheckState;
         }
@@ -1155,28 +1145,30 @@
          */
         @GuardedBy("mLock")
         public boolean isPendingHealthChecksLocked() {
-            return mHealthCheckState == STATE_ACTIVE || mHealthCheckState == STATE_INACTIVE;
+            return mHealthCheckState == HealthCheckState.ACTIVE
+                    || mHealthCheckState == HealthCheckState.INACTIVE;
         }
 
         /**
          * Updates the health check state based on {@link #mHasPassedHealthCheck}
          * and {@link #mHealthCheckDurationMs}.
          *
-         * @return the new health check state
+         * @return the new {@link HealthCheckState health check state}
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         private int updateHealthCheckStateLocked() {
             int oldState = mHealthCheckState;
             if (mHasPassedHealthCheck) {
                 // Set final state first to avoid ambiguity
-                mHealthCheckState = STATE_PASSED;
+                mHealthCheckState = HealthCheckState.PASSED;
             } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
                 // Set final state first to avoid ambiguity
-                mHealthCheckState = STATE_FAILED;
+                mHealthCheckState = HealthCheckState.FAILED;
             } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
-                mHealthCheckState = STATE_INACTIVE;
+                mHealthCheckState = HealthCheckState.INACTIVE;
             } else {
-                mHealthCheckState = STATE_ACTIVE;
+                mHealthCheckState = HealthCheckState.ACTIVE;
             }
             Slog.i(TAG, "Updated health check state for package " + mName + ": "
                     + toString(oldState) + " -> " + toString(mHealthCheckState));
@@ -1184,15 +1176,15 @@
         }
 
         /** Returns a {@link String} representation of the current health check state. */
-        private String toString(int state) {
+        private String toString(@HealthCheckState int state) {
             switch (state) {
-                case STATE_ACTIVE:
+                case HealthCheckState.ACTIVE:
                     return "ACTIVE";
-                case STATE_INACTIVE:
+                case HealthCheckState.INACTIVE:
                     return "INACTIVE";
-                case STATE_PASSED:
+                case HealthCheckState.PASSED:
                     return "PASSED";
-                case STATE_FAILED:
+                case HealthCheckState.FAILED:
                     return "FAILED";
                 default:
                     return "UNKNOWN";
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 1517887..997178e 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -25,15 +25,12 @@
 import android.os.RecoverySystem;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.system.ErrnoException;
-import android.system.Os;
 import android.util.Slog;
 
 import libcore.io.IoUtils;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
-import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 
@@ -288,7 +285,6 @@
                     byte[] cmdUtf8 = command.getBytes("UTF-8");
                     dos.writeInt(cmdUtf8.length);
                     dos.write(cmdUtf8, 0, cmdUtf8.length);
-                    dos.flush();
                 }
 
                 // Read the status from the socket.
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4fab7c1..8367e89 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -140,6 +140,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.storage.AppFuseBridge;
+import com.android.server.storage.StorageSessionController;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver;
 
@@ -498,6 +499,9 @@
     private final StorageManagerInternalImpl mStorageManagerInternal
             = new StorageManagerInternalImpl();
 
+    // Not guarded by a lock.
+    private final StorageSessionController mStorageSessionController;
+
     class ObbState implements IBinder.DeathRecipient {
         public ObbState(String rawPath, String canonicalPath, int callingUid,
                 IObbActionListener token, int nonce, String volId) {
@@ -647,12 +651,20 @@
                         Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
                         break;
                     }
-                    mount(vol);
+
+                    // TODO(b/135341433): Remove paranoid logging when FUSE is stable
+                    Slog.i(TAG, "Mounting volume " + vol);
+                    // TODO(b/135341433): Update to use new vold API that gets or mounts fuse fd
+                    // Ensure that we can pass user of a volume to the new API
+                    mStorageSessionController.onVolumeMounted(mCurrentUserId, mount(vol), vol);
+                    Slog.i(TAG, "Mounted volume " + vol);
+
                     break;
                 }
                 case H_VOLUME_UNMOUNT: {
                     final VolumeInfo vol = (VolumeInfo) msg.obj;
                     unmount(vol);
+                    mStorageSessionController.onVolumeUnmounted(mCurrentUserId, vol);
                     break;
                 }
                 case H_VOLUME_BROADCAST: {
@@ -733,6 +745,7 @@
                         }
                     }
                     mVold.onUserRemoved(userId);
+                    mStorageSessionController.onUserRemoved(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -951,7 +964,10 @@
             }
 
             try {
+                // TODO(b/135341433): Remove paranoid logging when FUSE is stable
+                Slog.i(TAG, "Resetting vold");
                 mVold.reset();
+                Slog.i(TAG, "Reset vold");
 
                 // Tell vold about all existing and started users
                 for (UserInfo user : users) {
@@ -976,6 +992,7 @@
         // staging area is ready so it's ready for zygote-forked apps to
         // bind mount against.
         try {
+            mStorageSessionController.onUserStarted(userId);
             mVold.onUserStarted(userId);
             mStoraged.onUserStarted(userId);
         } catch (Exception e) {
@@ -1516,6 +1533,12 @@
         // Add OBB Action Handler to StorageManagerService thread.
         mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
 
+        mStorageSessionController = new StorageSessionController(mContext,
+                userId -> {
+                    Slog.i(TAG, "Storage session ended for user: " + userId + ". Resetting...");
+                    mHandler.obtainMessage(H_RESET).sendToTarget();
+                });
+
         // Initialize the last-fstrim tracking if necessary
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
@@ -1814,11 +1837,18 @@
         mount(vol);
     }
 
-    private void mount(VolumeInfo vol) {
+    private FileDescriptor mount(VolumeInfo vol) {
         try {
-            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
+            // TODO(b/135341433): Now, emulated (and private?) volumes are shared across users
+            // This means the mountUserId on such volumes is USER_NULL. This breaks fuse which
+            // requires a valid user to mount a volume. Create individual volumes per user in vold
+            // and remove this property check
+            int userId = SystemProperties.getBoolean("persist.sys.fuse", false)
+                    ? mCurrentUserId : vol.mountUserId;
+            return mVold.mount(vol.id, vol.mountFlags, userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
+            return null;
         }
     }
 
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 4151c72..5e659b6 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -168,7 +168,7 @@
      * calls this method).
      */
     @Deprecated
-    public void onStartUser(@UserIdInt int userHandle) {}
+    public void onStartUser(@UserIdInt int userId) {}
 
     /**
      * Called when a new user is starting, for system services to initialize any per-user
@@ -189,7 +189,7 @@
      * default calls this method).
      */
     @Deprecated
-    public void onUnlockUser(@UserIdInt int userHandle) {}
+    public void onUnlockUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is in the process of being unlocked. This
@@ -218,7 +218,7 @@
      * (which by default calls this method).
      */
     @Deprecated
-    public void onSwitchUser(@UserIdInt int userHandle) {}
+    public void onSwitchUser(@UserIdInt int userId) {}
 
     /**
      * Called when switching to a different foreground user, for system services that have
@@ -243,7 +243,7 @@
      * calls this method).
      */
     @Deprecated
-    public void onStopUser(@UserIdInt int userHandle) {}
+    public void onStopUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is stopping, for system services to finalize any per-user
@@ -268,7 +268,7 @@
      * default calls this method).
      */
     @Deprecated
-    public void onCleanupUser(@UserIdInt int userHandle) {}
+    public void onCleanupUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is stopping, for system services to finalize any per-user
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f7e825e..c412ebd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -79,7 +79,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.stream.Collectors;
 
 /**
  * Since phone process can be restarted, this class provides a centralized place
@@ -285,11 +284,12 @@
                 }
                 case MSG_UPDATE_DEFAULT_SUB: {
                     int newDefaultPhoneId = msg.arg1;
-                    int newDefaultSubId = (Integer)(msg.obj);
+                    int newDefaultSubId = msg.arg2;
                     if (VDBG) {
                         log("MSG_UPDATE_DEFAULT_SUB:current mDefaultSubId=" + mDefaultSubId
-                            + " current mDefaultPhoneId=" + mDefaultPhoneId + " newDefaultSubId= "
-                            + newDefaultSubId + " newDefaultPhoneId=" + newDefaultPhoneId);
+                                + " current mDefaultPhoneId=" + mDefaultPhoneId
+                                + " newDefaultSubId=" + newDefaultSubId
+                                + " newDefaultPhoneId=" + newDefaultPhoneId);
                     }
 
                     //Due to possible risk condition,(notify call back using the new
@@ -306,7 +306,7 @@
                     mDefaultSubId = newDefaultSubId;
                     mDefaultPhoneId = newDefaultPhoneId;
                     mLocalLog.log("Default subscription updated: mDefaultPhoneId="
-                            + mDefaultPhoneId + ", mDefaultSubId" + mDefaultSubId);
+                            + mDefaultPhoneId + ", mDefaultSubId=" + mDefaultSubId);
                 }
             }
         }
@@ -336,22 +336,25 @@
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 if (DBG) log("onReceive: userHandle=" + userHandle);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, userHandle, 0));
-            } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)) {
-                Integer newDefaultSubIdObj = new Integer(intent.getIntExtra(
-                        PhoneConstants.SUBSCRIPTION_KEY,
-                        SubscriptionManager.getDefaultSubscriptionId()));
-                int newDefaultPhoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
-                    SubscriptionManager.getPhoneId(mDefaultSubId));
+            } else if (action.equals(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)) {
+                int newDefaultSubId = intent.getIntExtra(
+                        SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.getDefaultSubscriptionId());
+                int newDefaultPhoneId = intent.getIntExtra(
+                        PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.getPhoneId(newDefaultSubId));
                 if (DBG) {
                     log("onReceive:current mDefaultSubId=" + mDefaultSubId
-                        + " current mDefaultPhoneId=" + mDefaultPhoneId + " newDefaultSubId= "
-                        + newDefaultSubIdObj + " newDefaultPhoneId=" + newDefaultPhoneId);
+                            + " current mDefaultPhoneId=" + mDefaultPhoneId
+                            + " newDefaultSubId=" + newDefaultSubId
+                            + " newDefaultPhoneId=" + newDefaultPhoneId);
                 }
 
-                if(validatePhoneId(newDefaultPhoneId) && (!newDefaultSubIdObj.equals(mDefaultSubId)
-                        || (newDefaultPhoneId != mDefaultPhoneId))) {
+                if (validatePhoneId(newDefaultPhoneId)
+                        && (newDefaultSubId != mDefaultSubId
+                                || newDefaultPhoneId != mDefaultPhoneId)) {
                     mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_DEFAULT_SUB,
-                            newDefaultPhoneId, 0, newDefaultSubIdObj));
+                            newDefaultPhoneId, newDefaultSubId));
                 }
             }
         }
@@ -450,7 +453,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
-        filter.addAction(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+        filter.addAction(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
         log("systemRunning register for intents");
         mContext.registerReceiver(mBroadcastReceiver, filter);
     }
@@ -849,10 +852,7 @@
                         }
                     }
                     if ((events & PhoneStateListener
-                            .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0
-                            && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                                    r.context, r.callerPid, r.callerUid, r.callingPackage,
-                            "listen_active_data_subid_change")) {
+                            .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
                         try {
                             r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
                         } catch (RemoteException ex) {
@@ -951,13 +951,13 @@
         }
     }
 
-    public void notifyCallState(int state, String phoneNumber) {
+    public void notifyCallStateForAllSubs(int state, String phoneNumber) {
         if (!checkNotifyPermission("notifyCallState()")) {
             return;
         }
 
         if (VDBG) {
-            log("notifyCallState: state=" + state + " phoneNumber=" + phoneNumber);
+            log("notifyCallStateForAllSubs: state=" + state + " phoneNumber=" + phoneNumber);
         }
 
         synchronized (mRecords) {
@@ -984,13 +984,12 @@
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     }
 
-    public void notifyCallStateForPhoneId(int phoneId, int subId, int state,
-                String incomingNumber) {
+    public void notifyCallState(int phoneId, int subId, int state, String incomingNumber) {
         if (!checkNotifyPermission("notifyCallState()")) {
             return;
         }
         if (VDBG) {
-            log("notifyCallStateForPhoneId: subId=" + subId
+            log("notifyCallState: subId=" + subId
                 + " state=" + state + " incomingNumber=" + incomingNumber);
         }
         synchronized (mRecords) {
@@ -1084,10 +1083,10 @@
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 switch (activationType) {
-                    case PhoneConstants.SIM_ACTIVATION_TYPE_VOICE:
+                    case TelephonyManager.SIM_ACTIVATION_TYPE_VOICE:
                         mVoiceActivationState[phoneId] = activationState;
                         break;
-                    case PhoneConstants.SIM_ACTIVATION_TYPE_DATA:
+                    case TelephonyManager.SIM_ACTIVATION_TYPE_DATA:
                         mDataActivationState[phoneId] = activationState;
                         break;
                     default:
@@ -1100,7 +1099,7 @@
                                 + " state=" + activationState);
                     }
                     try {
-                        if ((activationType == PhoneConstants.SIM_ACTIVATION_TYPE_VOICE) &&
+                        if ((activationType == TelephonyManager.SIM_ACTIVATION_TYPE_VOICE) &&
                                 r.matchPhoneStateListenerEvent(
                                         PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) &&
                                 idMatch(r.subId, subId, phoneId)) {
@@ -1111,7 +1110,7 @@
                             }
                             r.callback.onVoiceActivationStateChanged(activationState);
                         }
-                        if ((activationType == PhoneConstants.SIM_ACTIVATION_TYPE_DATA) &&
+                        if ((activationType == TelephonyManager.SIM_ACTIVATION_TYPE_DATA) &&
                                 r.matchPhoneStateListenerEvent(
                                         PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) &&
                                 idMatch(r.subId, subId, phoneId)) {
@@ -1231,7 +1230,7 @@
     }
 
     public void notifyCellInfoForSubscriber(int subId, List<CellInfo> cellInfo) {
-        if (!checkNotifyPermission("notifyCellInfo()")) {
+        if (!checkNotifyPermission("notifyCellInfoForSubscriber()")) {
             return;
         }
         if (VDBG) {
@@ -1248,7 +1247,8 @@
                             checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
                         try {
                             if (DBG_LOC) {
-                                log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r);
+                                log("notifyCellInfoForSubscriber: mCellInfo=" + cellInfo
+                                    + " r=" + r);
                             }
                             r.callback.onCellInfoChanged(cellInfo);
                         } catch (RemoteException ex) {
@@ -1829,23 +1829,11 @@
             log("notifyActiveDataSubIdChanged: activeDataSubId=" + activeDataSubId);
         }
 
-        // Create a copy to prevent the IPC call while checking carrier privilege under the lock.
-        List<Record> copiedRecords;
-        synchronized (mRecords) {
-            copiedRecords = new ArrayList<>(mRecords);
-        }
         mActiveDataSubId = activeDataSubId;
-
-        // Filter the record that does not listen to this change or does not have the permission.
-        copiedRecords = copiedRecords.stream().filter(r -> r.matchPhoneStateListenerEvent(
-                PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)
-                && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                        mContext, r.callerPid, r.callerUid, r.callingPackage,
-                "notifyActiveDataSubIdChanged")).collect(Collectors.toCollection(ArrayList::new));
-
         synchronized (mRecords) {
-            for (Record r : copiedRecords) {
-                if (mRecords.contains(r)) {
+            for (Record r : mRecords) {
+                if (r.matchPhoneStateListenerEvent(
+                        PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) {
                     try {
                         r.callback.onActiveDataSubIdChanged(activeDataSubId);
                     } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7bc2e6d..08f75e6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -829,6 +829,15 @@
         }
     }
 
+    void killMisbehavingService(ServiceRecord r,
+            int appUid, int appPid, String localPackageName) {
+        synchronized (mAm) {
+            stopServiceLocked(r);
+            mAm.crashApplication(appUid, appPid, localPackageName, -1,
+                    "Bad notification for startForeground", true /*force*/);
+        }
+    }
+
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1739,8 +1748,8 @@
                     s.instanceName, s.processName);
             // Once the apps have become associated, if one of them is caller is ephemeral
             // the target app should now be able to see the calling app
-            mAm.grantEphemeralAccessLocked(callerApp.userId, service,
-                    UserHandle.getAppId(s.appInfo.uid), UserHandle.getAppId(callerApp.uid));
+            mAm.grantImplicitAccess(callerApp.userId, service,
+                    callerApp.uid, UserHandle.getAppId(s.appInfo.uid));
 
             AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
             ConnectionRecord c = new ConnectionRecord(b, activity,
@@ -2318,9 +2327,8 @@
         return true;
     }
 
+    /** @return {@code true} if the restart is scheduled. */
     private final boolean scheduleServiceRestartLocked(ServiceRecord r, boolean allowCancel) {
-        boolean canceled = false;
-
         if (mAm.mAtmInternal.isShuttingDown()) {
             Slog.w(TAG, "Not scheduling restart of crashed service " + r.shortInstanceName
                     + " - system is shutting down");
@@ -2337,10 +2345,12 @@
 
         final long now = SystemClock.uptimeMillis();
 
+        final String reason;
         if ((r.serviceInfo.applicationInfo.flags
                 &ApplicationInfo.FLAG_PERSISTENT) == 0) {
             long minDuration = mAm.mConstants.SERVICE_RESTART_DURATION;
             long resetTime = mAm.mConstants.SERVICE_RESET_RUN_DURATION;
+            boolean canceled = false;
 
             // Any delivered but not yet finished starts should be put back
             // on the pending list.
@@ -2367,6 +2377,17 @@
                 r.deliveredStarts.clear();
             }
 
+            if (allowCancel) {
+                final boolean shouldStop = r.canStopIfKilled(canceled);
+                if (shouldStop && !r.hasAutoCreateConnections()) {
+                    // Nothing to restart.
+                    return false;
+                }
+                reason = (r.startRequested && !shouldStop) ? "start-requested" : "connection";
+            } else {
+                reason = "always";
+            }
+
             r.totalRestartCount++;
             if (r.restartDelay == 0) {
                 r.restartCount++;
@@ -2418,6 +2439,7 @@
             r.restartCount = 0;
             r.restartDelay = 0;
             r.nextRestartTime = now;
+            reason = "persistent";
         }
 
         if (!mRestartingServices.contains(r)) {
@@ -2432,11 +2454,11 @@
         mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
         r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
         Slog.w(TAG, "Scheduling restart of crashed service "
-                + r.shortInstanceName + " in " + r.restartDelay + "ms");
+                + r.shortInstanceName + " in " + r.restartDelay + "ms for " + reason);
         EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
                 r.userId, r.shortInstanceName, r.restartDelay);
 
-        return canceled;
+        return true;
     }
 
     final void performServiceRestartLocked(ServiceRecord r) {
@@ -2780,8 +2802,9 @@
                 mAm.mUgmInternal.grantUriPermissionUncheckedFromIntent(si.neededGrants,
                         si.getUriPermissionsLocked());
             }
-            mAm.grantEphemeralAccessLocked(r.userId, si.intent, UserHandle.getAppId(r.appInfo.uid),
-                    UserHandle.getAppId(si.callingId));
+            mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
+                    UserHandle.getAppId(r.appInfo.uid)
+            );
             bumpServiceExecutingLocked(r, execInFg, "start");
             if (!oomAdjusted) {
                 oomAdjusted = true;
@@ -3651,22 +3674,21 @@
                     || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
                 bringDownServiceLocked(sr);
             } else {
-                boolean canceled = scheduleServiceRestartLocked(sr, true);
+                final boolean scheduled = scheduleServiceRestartLocked(sr, true /* allowCancel */);
 
                 // Should the service remain running?  Note that in the
                 // extreme case of so many attempts to deliver a command
                 // that it failed we also will stop it here.
-                if (sr.startRequested && (sr.stopIfKilled || canceled)) {
-                    if (sr.pendingStarts.size() == 0) {
-                        sr.startRequested = false;
-                        if (sr.tracker != null) {
-                            sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
-                                    SystemClock.uptimeMillis());
-                        }
-                        if (!sr.hasAutoCreateConnections()) {
-                            // Whoops, no reason to restart!
-                            bringDownServiceLocked(sr);
-                        }
+                if (!scheduled) {
+                    bringDownServiceLocked(sr);
+                } else if (sr.canStopIfKilled(false /* isStartCanceled */)) {
+                    // Update to stopped state because the explicit start is gone. The service is
+                    // scheduled to restart for other reason (e.g. connections) so we don't bring
+                    // down it.
+                    sr.startRequested = false;
+                    if (sr.tracker != null) {
+                        sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
+                                SystemClock.uptimeMillis());
                     }
                 }
             }
@@ -3918,7 +3940,7 @@
     void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
         mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
                 "Context.startForegroundService() did not then call Service.startForeground(): "
-                    + serviceRecord);
+                    + serviceRecord, false /*force*/);
     }
 
     void scheduleServiceTimeoutLocked(ProcessRecord proc) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 775a7dd..c07f67b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3568,7 +3568,7 @@
 
     @Override
     public void crashApplication(int uid, int initialPid, String packageName, int userId,
-            String message) {
+            String message, boolean force) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: crashApplication() from pid="
@@ -3580,7 +3580,8 @@
         }
 
         synchronized(this) {
-            mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
+            mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId,
+                    message, force);
         }
     }
 
@@ -4644,7 +4645,7 @@
         }
 
         boolean didSomething = mProcessList.killPackageProcessesLocked(packageName, appId, userId,
-                ProcessList.INVALID_ADJ, callerWillRestart, true /* allowRestart */, doit,
+                ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
                 evenPersistent, true /* setRemoved */,
                 packageName == null ? ("stop user " + userId) : ("stop " + packageName));
 
@@ -5264,11 +5265,14 @@
 
         // Inform checkpointing systems of success
         try {
+            // This line is needed to CTS test for the correct exception handling
+            // See b/138952436#comment36 for context
+            Slog.i(TAG, "About to commit checkpoint");
             IStorageManager storageManager = PackageHelper.getStorageManager();
             storageManager.commitChanges();
         } catch (Exception e) {
             PowerManager pm = (PowerManager)
-                     mContext.getSystemService(Context.POWER_SERVICE);
+                     mInjector.getContext().getSystemService(Context.POWER_SERVICE);
             pm.reboot("Checkpoint commit failed");
         }
 
@@ -6097,16 +6101,16 @@
         return ptw != null ? ptw.tag : null;
     }
 
-    private ProviderInfo getProviderInfoLocked(String authority, int userHandle, int pmFlags) {
+    private ProviderInfo getProviderInfoLocked(String authority, @UserIdInt int userId,
+            int pmFlags) {
         ProviderInfo pi = null;
-        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
+        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId);
         if (cpr != null) {
             pi = cpr.info;
         } else {
             try {
                 pi = AppGlobals.getPackageManager().resolveContentProvider(
-                        authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags,
-                        userHandle);
+                        authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags, userId);
             } catch (RemoteException ex) {
             }
         }
@@ -6114,10 +6118,9 @@
     }
 
     @VisibleForTesting
-    public void grantEphemeralAccessLocked(int userId, Intent intent,
-            int targetAppId, int ephemeralAppId) {
+    public void grantImplicitAccess(int userId, Intent intent, int callingUid, int targetAppId) {
         getPackageManagerInternalLocked().
-                grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
+                grantImplicitAccess(userId, intent, callingUid, targetAppId);
     }
 
     /**
@@ -6323,13 +6326,6 @@
     }
 
     @Override
-    public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration) {
-        mActivityTaskManager.resizeStack(stackId, destBounds, allowResizeInDockedMode,
-                preserveWindows, animate, animationDuration);
-    }
-
-    @Override
     public ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
             int userId) {
         return mActivityTaskManager.getRecentTasks(maxNum, flags, userId);
@@ -7094,9 +7090,10 @@
             }
             checkTime(startTime, "getContentProviderImpl: done!");
 
-            grantEphemeralAccessLocked(userId, null /*intent*/,
-                    UserHandle.getAppId(cpi.applicationInfo.uid),
-                    UserHandle.getAppId(Binder.getCallingUid()));
+            grantImplicitAccess(userId, null /*intent*/,
+                    UserHandle.getAppId(Binder.getCallingUid()),
+                    UserHandle.getAppId(cpi.applicationInfo.uid)
+            );
         }
 
         // Wait for the provider to be published...
@@ -8203,10 +8200,12 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
+    // TODO(b/137825297): Remove deprecated annotation and rephrase comments for all
+    // requestBugreport functions below.
     @Deprecated
     @Override
     public void requestBugReport(int bugreportType) {
@@ -8214,11 +8213,12 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
+    @Override
     public void requestBugReportWithDescription(@Nullable String shareTitle,
             @Nullable String shareDescription, int bugreportType) {
         String type = null;
@@ -8285,8 +8285,13 @@
             if (shareDescription != null) {
                 triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
             }
-            // Send broadcast to shell to trigger bugreport using Bugreport API
-            mContext.sendBroadcast(triggerShellBugreport);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // Send broadcast to shell to trigger bugreport using Bugreport API
+                mContext.sendBroadcast(triggerShellBugreport);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         } else {
             SystemProperties.set("dumpstate.options", type);
             SystemProperties.set("ctl.start", "bugreport");
@@ -8294,8 +8299,8 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
@@ -8306,8 +8311,8 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
@@ -8317,6 +8322,41 @@
                 ActivityManager.BUGREPORT_OPTION_WIFI);
     }
 
+    /**
+     * Takes an interactive bugreport with a progress notification
+     */
+    @Override
+    public void requestInteractiveBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+    }
+
+    /**
+     * Takes an interactive bugreport with a progress notification. Also, shows the given title and
+     * description on the final share notification
+     */
+    @Override
+    public void requestInteractiveBugReportWithDescription(String shareTitle,
+            String shareDescription) {
+        requestBugReportWithDescription(shareTitle, shareDescription,
+                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+    }
+
+    /**
+     * Takes a bugreport with minimal user interference
+     */
+    @Override
+    public void requestFullBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_FULL);
+    }
+
+    /**
+     * Takes a bugreport remotely
+     */
+    @Override
+    public void requestRemoteBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_REMOTE);
+    }
+
     public void registerProcessObserver(IProcessObserver observer) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerProcessObserver()");
@@ -9126,7 +9166,16 @@
                 Integer.toString(currentUserId), currentUserId);
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
                 Integer.toString(currentUserId), currentUserId);
-        mSystemServiceManager.startUser(t, currentUserId);
+
+        // On Automotive, at this point the system user has already been started and unlocked,
+        // and some of the tasks we do here have already been done. So skip those in that case.
+        // TODO(b/132262830): this workdound shouldn't be necessary once we move the
+        // headless-user start logic to UserManager-land
+        final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
+
+        if (bootingSystemUser) {
+            mSystemServiceManager.startUser(t, currentUserId);
+        }
 
         synchronized (this) {
             // Only start up encryption-aware persistent apps; once user is
@@ -9155,12 +9204,6 @@
                 t.traceEnd();
             }
 
-            // On Automotive, at this point the system user has already been started and unlocked,
-            // and some of the tasks we do here have already been done. So skip those in that case.
-            // TODO(b/132262830): this workdound shouldn't be necessary once we move the
-            // headless-user start logic to UserManager-land
-            final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
             if (bootingSystemUser) {
                 t.traceBegin("startHomeOnAllDisplays");
                 mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
@@ -13919,9 +13962,9 @@
         // Remove published content providers.
         for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
             ContentProviderRecord cpr = app.pubProviders.valueAt(i);
-            final boolean always = app.bad || !allowRestart;
-            boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
-            if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
+            final boolean alwaysRemove = app.bad || !allowRestart;
+            final boolean inLaunching = removeDyingProviderLocked(app, cpr, alwaysRemove);
+            if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
                 // We left the provider in the launching list, need to
                 // restart it.
                 restart = true;
@@ -15222,7 +15265,12 @@
                             final int uid = getUidFromIntent(intent);
                             if (uid >= 0) {
                                 mBatteryStatsService.removeUid(uid);
-                                mAppOpsService.uidRemoved(uid);
+                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+                                            intent.getData().getSchemeSpecificPart());
+                                } else {
+                                    mAppOpsService.uidRemoved(uid);
+                                }
                             }
                             break;
                         case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
@@ -15301,11 +15349,11 @@
                                     intent.getAction());
                             final String[] packageNames = intent.getStringArrayExtra(
                                     Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            final int userHandle = intent.getIntExtra(
+                            final int userIdExtra = intent.getIntExtra(
                                     Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
 
                             mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
-                                    userHandle);
+                                    userIdExtra);
                             break;
                     }
                     break;
@@ -18024,7 +18072,7 @@
         }
 
         @Override
-        public void killForegroundAppsForUser(int userHandle) {
+        public void killForegroundAppsForUser(@UserIdInt int userId) {
             synchronized (ActivityManagerService.this) {
                 final ArrayList<ProcessRecord> procs = new ArrayList<>();
                 final int NP = mProcessList.mProcessNames.getMap().size();
@@ -18039,7 +18087,7 @@
                             continue;
                         }
                         if (app.removed
-                                || (app.userId == userHandle && app.hasForegroundActivities())) {
+                                || (app.userId == userId && app.hasForegroundActivities())) {
                             procs.add(app);
                         }
                     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a0900b6..058afd3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1016,18 +1016,23 @@
 
     int runBugReport(PrintWriter pw) throws RemoteException {
         String opt;
-        int bugreportType = ActivityManager.BUGREPORT_OPTION_FULL;
+        boolean fullBugreport = true;
         while ((opt=getNextOption()) != null) {
             if (opt.equals("--progress")) {
-                bugreportType = ActivityManager.BUGREPORT_OPTION_INTERACTIVE;
+                fullBugreport = false;
+                mInterface.requestInteractiveBugReport();
             } else if (opt.equals("--telephony")) {
-                bugreportType = ActivityManager.BUGREPORT_OPTION_TELEPHONY;
+                fullBugreport = false;
+                // no title and description specified
+                mInterface.requestTelephonyBugReport("" /* no title */, "" /* no descriptions */);
             } else {
                 getErrPrintWriter().println("Error: Unknown option: " + opt);
                 return -1;
             }
         }
-        mInterface.requestBugReport(bugreportType);
+        if (fullBugreport) {
+            mInterface.requestFullBugReport();
+        }
         pw.println("Your lovely bug report is being created; please be patient.");
         return 0;
     }
@@ -1070,7 +1075,7 @@
         } catch (NumberFormatException e) {
             packageName = arg;
         }
-        mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash");
+        mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash", false);
         return 0;
     }
 
@@ -2481,8 +2486,6 @@
         switch (op) {
             case "move-task":
                 return runStackMoveTask(pw);
-            case "resize":
-                return runStackResize(pw);
             case "resize-animated":
                 return runStackResizeAnimated(pw);
             case "resize-docked-stack":
@@ -2561,17 +2564,6 @@
         return 0;
     }
 
-    int runStackResize(PrintWriter pw) throws RemoteException {
-        String stackIdStr = getNextArgRequired();
-        int stackId = Integer.parseInt(stackIdStr);
-        final Rect bounds = getBounds();
-        if (bounds == null) {
-            getErrPrintWriter().println("Error: invalid input bounds");
-            return -1;
-        }
-        return resizeStack(stackId, bounds, 0);
-    }
-
     int runStackResizeAnimated(PrintWriter pw) throws RemoteException {
         String stackIdStr = getNextArgRequired();
         int stackId = Integer.parseInt(stackIdStr);
@@ -2585,16 +2577,7 @@
                 return -1;
             }
         }
-        return resizeStackUnchecked(stackId, bounds, 0, true);
-    }
-
-    int resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate)
-            throws RemoteException {
-        try {
-            mTaskInterface.resizeStack(stackId, bounds, false, false, animate, -1);
-            Thread.sleep(delayMs);
-        } catch (InterruptedException e) {
-        }
+        mTaskInterface.animateResizePinnedStack(stackId, bounds, -1);
         return 0;
     }
 
@@ -2609,14 +2592,6 @@
         return 0;
     }
 
-    int resizeStack(int stackId, Rect bounds, int delayMs) throws RemoteException {
-        if (bounds == null) {
-            getErrPrintWriter().println("Error: invalid input bounds");
-            return -1;
-        }
-        return resizeStackUnchecked(stackId, bounds, delayMs, false);
-    }
-
     int runStackPositionTask(PrintWriter pw) throws RemoteException {
         String taskIdStr = getNextArgRequired();
         int taskId = Integer.parseInt(taskIdStr);
@@ -3195,8 +3170,6 @@
             pw.println("       move-task <TASK_ID> <STACK_ID> [true|false]");
             pw.println("           Move <TASK_ID> from its current stack to the top (true) or");
             pw.println("           bottom (false) of <STACK_ID>.");
-            pw.println("       resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("           Change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>.");
             pw.println("       resize-animated <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           Same as resize, but allow animation.");
             pw.println("       resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 1ff6f4d..6a29c75 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -314,20 +314,24 @@
     }
 
     void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
-        app.setCrashing(false);
-        app.crashingReport = null;
-        app.setNotResponding(false);
-        app.notRespondingReport = null;
         if (app.anrDialog == fromDialog) {
             app.anrDialog = null;
         }
         if (app.waitDialog == fromDialog) {
             app.waitDialog = null;
         }
+        killAppImmediateLocked(app, "user-terminated", "user request after error");
+    }
+
+    private void killAppImmediateLocked(ProcessRecord app, String reason, String killReason) {
+        app.setCrashing(false);
+        app.crashingReport = null;
+        app.setNotResponding(false);
+        app.notRespondingReport = null;
         if (app.pid > 0 && app.pid != MY_PID) {
-            handleAppCrashLocked(app, "user-terminated" /*reason*/,
+            handleAppCrashLocked(app, reason,
                     null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
-            app.kill("user request after error", true);
+            app.kill(killReason, true);
         }
     }
 
@@ -341,7 +345,7 @@
      * @param message
      */
     void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId,
-            String message) {
+            String message, boolean force) {
         ProcessRecord proc = null;
 
         // Figure out which process to kill.  We don't trust that initialPid
@@ -374,6 +378,14 @@
         }
 
         proc.scheduleCrash(message);
+        if (force) {
+            // If the app is responsive, the scheduled crash will happen as expected
+            // and then the delayed summary kill will be a no-op.
+            final ProcessRecord p = proc;
+            mService.mHandler.postDelayed(
+                    () -> killAppImmediateLocked(p, "forced", "killed for invalid state"),
+                    5000L);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index 95eb2c69..fd64df9 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -26,17 +26,12 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -61,8 +56,6 @@
     private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status";
     /** Path to procfs cmdline file. Used with pid: /proc/pid/cmdline. */
     private static final String PROC_CMDLINE_FILE_FMT = "/proc/%d/cmdline";
-    /** Path to debugfs file for the system ion heap. */
-    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
 
     private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)");
     private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)");
@@ -70,8 +63,6 @@
     private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
     private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");
 
-    private static final Pattern RSS_HIGH_WATERMARK_IN_KILOBYTES =
-            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_RSS_IN_KILOBYTES =
             Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_ANON_RSS_IN_KILOBYTES =
@@ -79,11 +70,6 @@
     private static final Pattern PROCFS_SWAP_IN_KILOBYTES =
             Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB");
 
-    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
-    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
-
     private static final int PGFAULT_INDEX = 9;
     private static final int PGMAJFAULT_INDEX = 11;
     private static final int START_TIME_INDEX = 21;
@@ -125,15 +111,6 @@
     }
 
     /**
-     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
-     * /proc/PID/status in bytes or 0 if not available.
-     */
-    public static long readRssHighWaterMarkFromProcfs(int pid) {
-        final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
-        return parseVmHWMFromProcfs(readFileContents(statusPath));
-    }
-
-    /**
      * Reads cmdline of a process from procfs.
      *
      * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
@@ -144,26 +121,6 @@
         return parseCmdlineFromProcfs(readFileContents(path));
     }
 
-    /**
-     * Reads size of the system ion heap from debugfs.
-     *
-     * Returns value of the total size in bytes of the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    public static long readSystemIonHeapSizeFromDebugfs() {
-        return parseIonHeapSizeFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
-    /**
-     * Reads process allocation sizes on the system ion heap from debugfs.
-     *
-     * Returns values of allocation sizes in bytes on the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
-        return parseProcessIonHeapSizesFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
     private static String readFileContents(String path) {
         final File file = new File(path);
         if (!file.exists()) {
@@ -236,21 +193,6 @@
     }
 
     /**
-     * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The
-     * returned value is in bytes.
-     */
-    @VisibleForTesting
-    static long parseVmHWMFromProcfs(String procStatusContents) {
-        if (procStatusContents == null || procStatusContents.isEmpty()) {
-            return 0;
-        }
-        // Convert value read from /proc/pid/status from kilobytes to bytes.
-        return tryParseLong(RSS_HIGH_WATERMARK_IN_KILOBYTES, procStatusContents)
-                * BYTES_IN_KILOBYTE;
-    }
-
-
-    /**
      * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
      *
      * Parsing is required to strip anything after first null byte.
@@ -268,55 +210,6 @@
     }
 
     /**
-     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
-     * debugfs. The returned value is in bytes.
-     */
-    @VisibleForTesting
-    static long parseIonHeapSizeFromDebugfs(String contents) {
-        if (contents == null || contents.isEmpty()) {
-            return 0;
-        }
-        return tryParseLong(ION_HEAP_SIZE_IN_BYTES, contents);
-    }
-
-    /**
-     * Parses per-process allocation sizes on the ion heap from the contents of a file under
-     * /sys/kernel/debug/ion/heaps in debugfs.
-     */
-    @VisibleForTesting
-    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
-        if (contents == null || contents.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
-        final SparseArray<IonAllocations> entries = new SparseArray<>();
-        while (m.find()) {
-            try {
-                final int pid = Integer.parseInt(m.group(1));
-                final long sizeInBytes = Long.parseLong(m.group(2));
-                IonAllocations allocations = entries.get(pid);
-                if (allocations == null) {
-                    allocations = new IonAllocations();
-                    entries.put(pid, allocations);
-                }
-                allocations.pid = pid;
-                allocations.totalSizeInBytes += sizeInBytes;
-                allocations.count += 1;
-                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Failed to parse value", e);
-            }
-        }
-
-        final List<IonAllocations> result = new ArrayList<>(entries.size());
-        for (int i = 0; i < entries.size(); i++) {
-            result.add(entries.valueAt(i));
-        }
-        return result;
-    }
-
-    /**
      * Returns whether per-app memcg is available on device.
      */
     static boolean hasMemcg() {
@@ -353,40 +246,4 @@
         /** Device time when the processes started. */
         public long startTimeNanos;
     }
-
-    /** Summary information about process ion allocations. */
-    public static final class IonAllocations {
-        /** PID these allocations belong to. */
-        public int pid;
-        /** Size of all individual allocations added together. */
-        public long totalSizeInBytes;
-        /** Number of allocations. */
-        public int count;
-        /** Size of the largest allocation. */
-        public long maxSizeInBytes;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            IonAllocations that = (IonAllocations) o;
-            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
-                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
-        }
-
-        @Override
-        public String toString() {
-            return "IonAllocations{"
-                    + "pid=" + pid
-                    + ", totalSizeInBytes=" + totalSizeInBytes
-                    + ", count=" + count
-                    + ", maxSizeInBytes=" + maxSizeInBytes
-                    + '}';
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 0509f9f..2f9a5c9 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -12,6 +12,7 @@
 varunshah@google.com
 kwekua@google.com
 bookatz@google.com
+jji@google.com
 
 # Windows & Activities
 ogunwale@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7a3d3c2..3c2aee4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1576,7 +1576,7 @@
         }
 
         if (adj == ProcessList.SERVICE_ADJ) {
-            if (doingAll) {
+            if (doingAll && !cycleReEval) {
                 app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
                 mNewNumServiceProcs++;
                 //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8619ad5..cc4b160 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -19,9 +19,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.app.INotificationManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -33,7 +31,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -623,6 +620,14 @@
         }
     }
 
+    /**
+     * @return {@code true} if the killed service which was started by {@link Context#startService}
+     *         has no reason to start again. Note this condition doesn't consider the bindings.
+     */
+    boolean canStopIfKilled(boolean isStartCanceled) {
+        return startRequested && (stopIfKilled || isStartCanceled) && pendingStarts.isEmpty();
+    }
+
     void updateHasBindingWhitelistingBgActivityStarts() {
         boolean hasWhitelistingBinding = false;
         for (int conni = connections.size() - 1; conni >= 0; conni--) {
@@ -798,6 +803,7 @@
             final String localPackageName = packageName;
             final int localForegroundId = foregroundId;
             final Notification _foregroundNoti = foregroundNoti;
+            final ServiceRecord record = this;
             ams.mHandler.post(new Runnable() {
                 public void run() {
                     NotificationManagerInternal nm = LocalServices.getService(
@@ -896,10 +902,8 @@
                         Slog.w(TAG, "Error showing notification for service", e);
                         // If it gave us a garbage notification, it doesn't
                         // get to be foreground.
-                        ams.setServiceForeground(instanceName, ServiceRecord.this,
-                                0, null, 0, 0);
-                        ams.crashApplication(appUid, appPid, localPackageName, -1,
-                                "Bad notification for startForeground: " + e);
+                        ams.mServices.killMisbehavingService(record,
+                                appUid, appPid, localPackageName);
                     }
                 }
             });
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5fe72dd..5c8e530 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -841,7 +841,7 @@
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
-    private int updateUserToLockLU(int userId) {
+    private int updateUserToLockLU(@UserIdInt int userId) {
         int userIdToLock = userId;
         if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
@@ -869,7 +869,7 @@
      * {@code userId}. The returned list includes {@code userId}.
      */
     @GuardedBy("mLock")
-    private @NonNull int[] getUsersToStopLU(int userId) {
+    private @NonNull int[] getUsersToStopLU(@UserIdInt int userId) {
         int startedUsersSize = mStartedUsers.size();
         IntArray userIds = new IntArray();
         userIds.add(userId);
@@ -892,7 +892,7 @@
         return userIds.toArray();
     }
 
-    private void forceStopUser(int userId, String reason) {
+    private void forceStopUser(@UserIdInt int userId, String reason) {
         mInjector.activityManagerForceStopPackage(userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -965,7 +965,7 @@
         }
     }
 
-    boolean startUser(final int userId, final boolean foreground) {
+    boolean startUser(final @UserIdInt int userId, final boolean foreground) {
         return startUser(userId, foreground, null);
     }
 
@@ -1002,7 +1002,7 @@
      * @return true if the user has been successfully started
      */
     boolean startUser(
-            final int userId,
+            final @UserIdInt int userId,
             final boolean foreground,
             @Nullable IProgressListener unlockListener) {
 
@@ -1018,7 +1018,7 @@
         }
     }
 
-    private boolean startUserInternal(int userId, boolean foreground,
+    private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
             @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
         Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);
 
@@ -1257,7 +1257,8 @@
         }
     }
 
-    boolean unlockUser(final int userId, byte[] token, byte[] secret, IProgressListener listener) {
+    boolean unlockUser(final @UserIdInt int userId, byte[] token, byte[] secret,
+            IProgressListener listener) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "unlockUser");
         final long binderToken = Binder.clearCallingIdentity();
         try {
@@ -1273,12 +1274,12 @@
      * when the credential-encrypted storage isn't tied to a user-provided
      * PIN or pattern.
      */
-    private boolean maybeUnlockUser(final int userId) {
+    private boolean maybeUnlockUser(final @UserIdInt int userId) {
         // Try unlocking storage using empty token
         return unlockUserCleared(userId, null, null, null);
     }
 
-    private static void notifyFinished(int userId, IProgressListener listener) {
+    private static void notifyFinished(@UserIdInt int userId, IProgressListener listener) {
         if (listener == null) return;
         try {
             listener.onFinished(userId, null);
@@ -1286,7 +1287,7 @@
         }
     }
 
-    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+    private boolean unlockUserCleared(final @UserIdInt int userId, byte[] token, byte[] secret,
             IProgressListener listener) {
         UserState uss;
         if (!StorageManager.isUserKeyUnlocked(userId)) {
@@ -1384,7 +1385,7 @@
                 getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
     }
 
-    private void dispatchForegroundProfileChanged(int userId) {
+    private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1397,7 +1398,7 @@
     }
 
     /** Called on handler thread */
-    void dispatchUserSwitchComplete(int userId) {
+    void dispatchUserSwitchComplete(@UserIdInt int userId) {
         mInjector.getWindowManager().setSwitchingUser(false);
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
@@ -1409,7 +1410,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void dispatchLockedBootComplete(int userId) {
+    private void dispatchLockedBootComplete(@UserIdInt int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1596,7 +1597,7 @@
     }
 
 
-    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+    int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId, boolean allowAll,
             int allowMode, String name, String callerPackage) {
         final int callingUserId = UserHandle.getUserId(callingUid);
         if (callingUserId == userId) {
@@ -1682,12 +1683,12 @@
         return targetUserId;
     }
 
-    int unsafeConvertIncomingUser(int userId) {
+    int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
     }
 
-    void ensureNotSpecialUser(int userId) {
+    void ensureNotSpecialUser(@UserIdInt int userId) {
         if (userId >= 0) {
             return;
         }
@@ -1700,7 +1701,7 @@
         mUserSwitchObservers.register(observer, name);
     }
 
-    void sendForegroundProfileChanged(int userId) {
+    void sendForegroundProfileChanged(@UserIdInt int userId) {
         mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
         mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
     }
@@ -1709,13 +1710,13 @@
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserState(int userId) {
+    UserState getStartedUserState(@UserIdInt int userId) {
         synchronized (mLock) {
             return mStartedUsers.get(userId);
         }
     }
 
-    boolean hasStartedUserState(int userId) {
+    boolean hasStartedUserState(@UserIdInt int userId) {
         synchronized (mLock) {
             return mStartedUsers.get(userId) != null;
         }
@@ -1804,7 +1805,7 @@
         }
     }
 
-    boolean isUserRunning(int userId, int flags) {
+    boolean isUserRunning(@UserIdInt int userId, int flags) {
         UserState state = getStartedUserState(userId);
         if (state == null) {
             return false;
@@ -1920,7 +1921,7 @@
     }
 
     @GuardedBy("mLock")
-    private boolean isCurrentUserLU(int userId) {
+    private boolean isCurrentUserLU(@UserIdInt int userId) {
         return userId == getCurrentOrTargetUserIdLU();
     }
 
@@ -1929,7 +1930,7 @@
         return ums != null ? ums.getUserIds() : new int[] { 0 };
     }
 
-    private UserInfo getUserInfo(int userId) {
+    private UserInfo getUserInfo(@UserIdInt int userId) {
         return mInjector.getUserManager().getUserInfo(userId);
     }
 
@@ -1943,7 +1944,7 @@
      *
      * It doesn't handle other special user IDs such as {@link UserHandle#USER_CURRENT}.
      */
-    int[] expandUserId(int userId) {
+    int[] expandUserId(@UserIdInt int userId) {
         if (userId != UserHandle.USER_ALL) {
             return new int[] {userId};
         } else {
@@ -1951,7 +1952,7 @@
         }
     }
 
-    boolean exists(int userId) {
+    boolean exists(@UserIdInt int userId) {
         return mInjector.getUserManager().exists(userId);
     }
 
@@ -1967,16 +1968,16 @@
         }
     }
 
-    private void enforceShellRestriction(String restriction, int userHandle) {
+    private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
         if (Binder.getCallingUid() == SHELL_UID) {
-            if (userHandle < 0 || hasUserRestriction(restriction, userHandle)) {
+            if (userId < 0 || hasUserRestriction(restriction, userId)) {
                 throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
+                        + userId);
             }
         }
     }
 
-    boolean hasUserRestriction(String restriction, int userId) {
+    boolean hasUserRestriction(String restriction, @UserIdInt int userId) {
         return mInjector.getUserManager().hasUserRestriction(restriction, userId);
     }
 
@@ -1994,7 +1995,7 @@
         }
     }
 
-    boolean isUserOrItsParentRunning(int userId) {
+    boolean isUserOrItsParentRunning(@UserIdInt int userId) {
         synchronized (mLock) {
             if (isUserRunning(userId, 0)) {
                 return true;
@@ -2007,7 +2008,7 @@
         }
     }
 
-    boolean isCurrentProfile(int userId) {
+    boolean isCurrentProfile(@UserIdInt int userId) {
         synchronized (mLock) {
             return ArrayUtils.contains(mCurrentProfileIds, userId);
         }
@@ -2019,7 +2020,7 @@
         }
     }
 
-    void onUserRemoved(int userId) {
+    void onUserRemoved(@UserIdInt int userId) {
         synchronized (mLock) {
             int size = mUserProfileGroupIds.size();
             for (int i = size - 1; i >= 0; i--) {
@@ -2037,7 +2038,7 @@
      * Returns whether the given user requires credential entry at this time. This is used to
      * intercept activity launches for work apps when the Work Challenge is present.
      */
-    protected boolean shouldConfirmCredentials(int userId) {
+    protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
         synchronized (mLock) {
             if (mStartedUsers.get(userId) == null) {
                 return false;
@@ -2265,7 +2266,7 @@
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
-                int realCallingPid, int userId) {
+                int realCallingPid, @UserIdInt int userId) {
             // TODO b/64165549 Verify that mLock is not held before calling AMS methods
             synchronized (mService) {
                 return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
@@ -2282,11 +2283,11 @@
         WindowManagerService getWindowManager() {
             return mService.mWindowManager;
         }
-        void activityManagerOnUserStopped(int userId) {
+        void activityManagerOnUserStopped(@UserIdInt int userId) {
             LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
         }
 
-        void systemServiceManagerCleanupUser(int userId) {
+        void systemServiceManagerCleanupUser(@UserIdInt int userId) {
             mService.mSystemServiceManager.cleanupUser(userId);
         }
 
@@ -2330,7 +2331,7 @@
             }
         }
 
-        void sendPreBootBroadcast(int userId, boolean quiet, final Runnable onFinish) {
+        void sendPreBootBroadcast(@UserIdInt int userId, boolean quiet, final Runnable onFinish) {
             new PreBootBroadcaster(mService, userId, null, quiet) {
                 @Override
                 public void onFinished() {
@@ -2339,7 +2340,7 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackage(int userId, String reason) {
+        void activityManagerForceStopPackage(@UserIdInt int userId, String reason) {
             synchronized (mService) {
                 mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
                         userId, reason);
@@ -2351,11 +2352,11 @@
             return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
         }
 
-        protected void startHomeActivity(int userId, String reason) {
+        protected void startHomeActivity(@UserIdInt int userId, String reason) {
             mService.mAtmInternal.startHomeActivity(userId, reason);
         }
 
-        void startUserWidgets(int userId) {
+        void startUserWidgets(@UserIdInt int userId) {
             AppWidgetManagerInternal awm = LocalServices.getService(AppWidgetManagerInternal.class);
             if (awm != null) {
                 // Out of band, because this is called during a sequence with
@@ -2370,13 +2371,13 @@
             mService.mAtmInternal.updateUserConfiguration();
         }
 
-        void clearBroadcastQueueForUser(int userId) {
+        void clearBroadcastQueueForUser(@UserIdInt int userId) {
             synchronized (mService) {
                 mService.clearBroadcastQueueForUserLocked(userId);
             }
         }
 
-        void loadUserRecents(int userId) {
+        void loadUserRecents(@UserIdInt int userId) {
             mService.mAtmInternal.loadRecentTasksForUser(userId);
         }
 
@@ -2384,7 +2385,7 @@
             mService.startPersistentApps(matchFlags);
         }
 
-        void installEncryptionUnawareProviders(int userId) {
+        void installEncryptionUnawareProviders(@UserIdInt int userId) {
             mService.installEncryptionUnawareProviders(userId);
         }
 
@@ -2417,11 +2418,11 @@
             }
         }
 
-        void stackSupervisorRemoveUser(int userId) {
+        void stackSupervisorRemoveUser(@UserIdInt int userId) {
             mService.mAtmInternal.removeUser(userId);
         }
 
-        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+        protected boolean stackSupervisorSwitchUser(@UserIdInt int userId, UserState uss) {
             return mService.mAtmInternal.switchUser(userId, uss);
         }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6d6a148..f866314 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -31,9 +31,11 @@
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -49,6 +51,7 @@
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
+import android.app.AsyncNotedAppOp;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -58,9 +61,10 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
-import android.media.AudioAttributes;
+import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -69,6 +73,7 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -86,6 +91,7 @@
 import android.util.KeyValueListParser;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -96,6 +102,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
@@ -181,6 +188,8 @@
             OP_CAMERA,
     };
 
+    private static final int MAX_UNFORWARED_OPS = 10;
+
     Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
@@ -188,6 +197,29 @@
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
 
+    /**
+     * Registered callbacks, called from {@link #noteAsyncOp}.
+     *
+     * <p>(package name, uid) -> callbacks
+     *
+     * @see #getAsyncNotedOpsKey(String, int)
+     */
+    @GuardedBy("this")
+    private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
+            mAsyncOpWatchers = new ArrayMap<>();
+
+    /**
+     * Async note-ops collected from {@link #noteAsyncOp} that have not been delivered to a
+     * callback yet.
+     *
+     * <p>(package name, uid) -> list&lt;ops&gt;
+     *
+     * @see #getAsyncNotedOpsKey(String, int)
+     */
+    @GuardedBy("this")
+    private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
+            mUnforwardedAsyncNotedOps = new ArrayMap<>();
+
     boolean mWriteScheduled;
     boolean mFastWriteScheduled;
     final Runnable mWriteRunner = new Runnable() {
@@ -328,7 +360,7 @@
 
         public int startNesting;
         public ArrayMap<String, Ops> pkgOps;
-        public SparseIntArray opModes;
+        private SparseIntArray opModes;
 
         // true indicates there is an interested observer, false there isn't but it has such an op
         public SparseBooleanArray foregroundOps;
@@ -350,6 +382,64 @@
                     && (pendingState == UID_STATE_CACHED));
         }
 
+        public int getOpModeCount() {
+            return opModes != null ? opModes.size() : 0;
+        }
+
+        public int getOpCodeAt(int index) {
+            return opModes.keyAt(index);
+        }
+
+        public boolean hasOpMode(int code) {
+            return opModes != null && opModes.indexOfKey(code) >= 0;
+        }
+
+        public int getOpMode(int code) {
+            return opModes.get(code);
+        }
+
+        public boolean putOpMode(int code, int mode) {
+            if (mode == AppOpsManager.opToDefaultMode(code)) {
+                return removeOpMode(code);
+            }
+            if (opModes == null) {
+                opModes = new SparseIntArray();
+            }
+            int index = opModes.indexOfKey(code);
+            if (index < 0) {
+                opModes.put(code, mode);
+                return true;
+            }
+            if (opModes.valueAt(index) == mode) {
+                return false;
+            }
+            opModes.setValueAt(index, mode);
+            return true;
+        }
+
+        public boolean removeOpMode(int code) {
+            if (opModes == null) {
+                return false;
+            }
+            int index = opModes.indexOfKey(code);
+            if (index < 0) {
+                return false;
+            }
+            opModes.removeAt(index);
+            if (opModes.size() == 0) {
+                opModes = null;
+            }
+            return true;
+        }
+
+        @Nullable
+        public SparseIntArray cloneOpModes() {
+            if (opModes == null) {
+                return null;
+            }
+            return opModes.clone();
+        }
+
         int evalMode(int op, int mode) {
             if (mode == AppOpsManager.MODE_FOREGROUND) {
                 return state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)
@@ -377,14 +467,13 @@
         public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) {
             SparseBooleanArray which = null;
             hasForegroundWatchers = false;
-            if (opModes != null) {
-                for (int i = opModes.size() - 1; i >= 0; i--) {
-                    if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
-                        if (which == null) {
-                            which = new SparseBooleanArray();
-                        }
-                        evalForegroundWatchers(opModes.keyAt(i), watchers, which);
+            for (int i = getOpModeCount() - 1; i >= 0; i--) {
+                int code = getOpCodeAt(i);
+                if (getOpMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                    if (which == null) {
+                        which = new SparseBooleanArray();
                     }
+                    evalForegroundWatchers(code, watchers, which);
                 }
             }
             if (pkgOps != null) {
@@ -519,6 +608,10 @@
 
         private void updateProxyState(long key, int proxyUid,
             @Nullable String proxyPackageName) {
+            if (proxyUid == Process.INVALID_UID) {
+                return;
+            }
+
             if (mProxyUids == null) {
                 mProxyUids = new LongSparseLongArray();
             }
@@ -540,7 +633,7 @@
     final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
-    final SparseArray<SparseArray<Restriction>> mAudioRestrictions = new SparseArray<>();
+    final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
     final class ModeCallback implements DeathRecipient {
         final IAppOpsCallback mCallback;
@@ -714,7 +807,12 @@
         public void binderDied() {
             synchronized (AppOpsService.this) {
                 for (int i=mStartedOps.size()-1; i>=0; i--) {
-                    finishOperationLocked(mStartedOps.get(i), /*finishNested*/ true);
+                    final Op op = mStartedOps.get(i);
+                    finishOperationLocked(op, /*finishNested*/ true);
+                    if (op.startNesting <= 0) {
+                        scheduleOpActiveChangedIfNeededLocked(op.op, op.uidState.uid,
+                                op.packageName, false);
+                    }
                 }
                 mClients.remove(mAppToken);
             }
@@ -793,20 +891,20 @@
                 final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 final String[] changedPkgs = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                ArraySet<ModeCallback> callbacks;
-                synchronized (AppOpsService.this) {
-                    callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO);
-                    if (callbacks == null) {
-                        return;
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<ModeCallback> callbacks;
+                    synchronized (AppOpsService.this) {
+                        callbacks = mOpModeWatchers.get(code);
+                        if (callbacks == null) {
+                            continue;
+                        }
+                        callbacks = new ArraySet<>(callbacks);
                     }
-                    callbacks = new ArraySet<>(callbacks);
-                }
-                for (int i = 0; i < changedUids.length; i++) {
-                    final int changedUid = changedUids[i];
-                    final String changedPkg = changedPkgs[i];
-                    // We trust packagemanager to insert matching uid and packageNames in the
-                    // extras
-                    for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
                         notifyOpChanged(callbacks, code, changedUid, changedPkg);
                     }
                 }
@@ -1023,24 +1121,28 @@
         return resOps;
     }
 
-    private ArrayList<AppOpsManager.OpEntry> collectOps(SparseIntArray uidOps, int[] ops) {
-        if (uidOps == null) {
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        int opModeCount = uidState.getOpModeCount();
+        if (opModeCount == 0) {
             return null;
         }
         ArrayList<AppOpsManager.OpEntry> resOps = null;
         if (ops == null) {
             resOps = new ArrayList<>();
-            for (int j=0; j<uidOps.size(); j++) {
-                resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+            for (int i = 0; i < opModeCount; i++) {
+                int code = uidState.getOpCodeAt(i);
+                resOps.add(new OpEntry(code, uidState.getOpMode(code)));
             }
         } else {
-            for (int j=0; j<ops.length; j++) {
-                int index = uidOps.indexOfKey(ops[j]);
-                if (index >= 0) {
+            for (int i = 0; i < ops.length; i++) {
+                int code = ops[i];
+                if (uidState.hasOpMode(code)) {
                     if (resOps == null) {
                         resOps = new ArrayList<>();
                     }
-                    resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+                    resOps.add(new OpEntry(code, uidState.getOpMode(code)));
                 }
             }
         }
@@ -1186,11 +1288,11 @@
             if (uidState == null) {
                 return null;
             }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState.opModes, ops);
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState, ops);
             if (resOps == null) {
                 return null;
             }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
             AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
                     null, uidState.uid, resOps);
             res.add(resPackage);
@@ -1258,29 +1360,14 @@
                     return;
                 }
                 uidState = new UidState(uid);
-                uidState.opModes = new SparseIntArray();
-                uidState.opModes.put(code, mode);
+                uidState.putOpMode(code, mode);
                 mUidStates.put(uid, uidState);
                 scheduleWriteLocked();
-            } else if (uidState.opModes == null) {
-                if (mode != defaultMode) {
-                    uidState.opModes = new SparseIntArray();
-                    uidState.opModes.put(code, mode);
+            } else {
+                boolean changed = uidState.putOpMode(code, mode);
+                if (changed) {
                     scheduleWriteLocked();
                 }
-            } else {
-                if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
-                    return;
-                }
-                if (mode == defaultMode) {
-                    uidState.opModes.delete(code);
-                    if (uidState.opModes.size() <= 0) {
-                        uidState.opModes = null;
-                    }
-                } else {
-                    uidState.opModes.put(code, mode);
-                }
-                scheduleWriteLocked();
             }
             uidState.evalForegroundOps(mOpModeWatchers);
         }
@@ -1519,16 +1606,13 @@
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
                 UidState uidState = mUidStates.valueAt(i);
 
-                SparseIntArray opModes = uidState.opModes;
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
+                if (uidState.uid == reqUid || reqUid == -1) {
+                    for (int opModeIndex = uidState.getOpModeCount() - 1; opModeIndex >= 0;
+                            opModeIndex--) {
+                        final int code = uidState.getOpCodeAt(opModeIndex);
+
                         if (AppOpsManager.opAllowsReset(code)) {
-                            opModes.removeAt(j);
-                            if (opModes.size() <= 0) {
-                                uidState.opModes = null;
-                            }
+                            uidState.removeOpMode(code);
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mOpModeWatchers.get(code));
@@ -1778,9 +1862,8 @@
             }
             code = AppOpsManager.opToSwitch(code);
             UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null && uidState.opModes != null
-                    && uidState.opModes.indexOfKey(code) >= 0) {
-                final int rawMode = uidState.opModes.get(code);
+            if (uidState != null && uidState.hasOpMode(code)) {
+                final int rawMode = uidState.getOpMode(code);
                 return raw ? rawMode : uidState.evalMode(code, rawMode);
             }
             Op op = getOpLocked(code, uid, packageName, false, false);
@@ -1805,60 +1888,43 @@
     }
 
     private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) {
-        synchronized (this) {
-            final int mode = checkRestrictionLocked(code, usage, uid, packageName);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                return mode;
-            }
+        final int mode = mAudioRestrictionManager.checkAudioOperation(
+                code, usage, uid, packageName);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            return mode;
         }
         return checkOperation(code, uid, packageName);
     }
 
-    private int checkRestrictionLocked(int code, int usage, int uid, String packageName) {
-        final SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
-        if (usageRestrictions != null) {
-            final Restriction r = usageRestrictions.get(usage);
-            if (r != null && !r.exceptionPackages.contains(packageName)) {
-                return r.mode;
-            }
-        }
-        return AppOpsManager.MODE_ALLOWED;
-    }
-
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
         enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        synchronized (this) {
-            SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
-            if (usageRestrictions == null) {
-                usageRestrictions = new SparseArray<Restriction>();
-                mAudioRestrictions.put(code, usageRestrictions);
-            }
-            usageRestrictions.remove(usage);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                final Restriction r = new Restriction();
-                r.mode = mode;
-                if (exceptionPackages != null) {
-                    final int N = exceptionPackages.length;
-                    r.exceptionPackages = new ArraySet<String>(N);
-                    for (int i = 0; i < N; i++) {
-                        final String pkg = exceptionPackages[i];
-                        if (pkg != null) {
-                            r.exceptionPackages.add(pkg.trim());
-                        }
-                    }
-                }
-                usageRestrictions.put(usage, r);
-            }
-        }
+
+        mAudioRestrictionManager.setZenModeAudioRestriction(
+                code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
     }
 
+
+    @Override
+    public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+
+        mAudioRestrictionManager.setCameraAudioRestriction(mode);
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsManager.OP_VIBRATE, UID_ANY));
+    }
+
     @Override
     public int checkPackage(int uid, String packageName) {
         Preconditions.checkNotNull(packageName);
@@ -1966,8 +2032,8 @@
             final int switchCode = AppOpsManager.opToSwitch(code);
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+            if (uidState.hasOpMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
@@ -2098,6 +2164,141 @@
     }
 
     @Override
+    public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+            String message) {
+        Preconditions.checkNotNull(message);
+        Preconditions.checkNotNull(packageName);
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        verifyIncomingUid(uid);
+        verifyIncomingOp(opCode);
+
+        int callingUid = Binder.getCallingUid();
+        long now = System.currentTimeMillis();
+
+        if (callingPackageName != null) {
+            verifyAndGetIsPrivileged(callingUid, callingPackageName);
+        }
+
+        long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+                AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+                        callingPackageName, message, now);
+                final boolean[] wasNoteForwarded = {false};
+
+                if (callbacks != null) {
+                    callbacks.broadcast((cb) -> {
+                        try {
+                            cb.opNoted(asyncNotedOp);
+                            wasNoteForwarded[0] = true;
+                        } catch (RemoteException e) {
+                            Slog.e(TAG,
+                                    "Could not forward noteOp of " + opCode + " to " + packageName
+                                            + "/" + uid, e);
+                        }
+                    });
+                }
+
+                if (!wasNoteForwarded[0]) {
+                    ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
+                    if (unforwardedOps == null) {
+                        unforwardedOps = new ArrayList<>(1);
+                        mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+                    }
+
+                    unforwardedOps.add(asyncNotedOp);
+                    if (unforwardedOps.size() > MAX_UNFORWARED_OPS) {
+                        unforwardedOps.remove(0);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
+     *
+     * @param packageName The package name of the app
+     * @param uid The uid of the app
+     *
+     * @return They key uniquely identifying the app
+     */
+    private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
+            int uid) {
+        return new Pair<>(packageName, uid);
+    }
+
+    @Override
+    public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(callback);
+
+        int uid = Binder.getCallingUid();
+        Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+            if (callbacks == null) {
+                callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
+                    @Override
+                    public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
+                        synchronized (AppOpsService.this) {
+                            if (getRegisteredCallbackCount() == 0) {
+                                mAsyncOpWatchers.remove(key);
+                            }
+                        }
+                    }
+                };
+                mAsyncOpWatchers.put(key, callbacks);
+            }
+
+            callbacks.register(callback);
+        }
+    }
+
+    @Override
+    public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(callback);
+
+        int uid = Binder.getCallingUid();
+        Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+            if (callbacks != null) {
+                callbacks.unregister(callback);
+                if (callbacks.getRegisteredCallbackCount() == 0) {
+                    mAsyncOpWatchers.remove(key);
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
+        Preconditions.checkNotNull(packageName);
+
+        int uid = Binder.getCallingUid();
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
+        }
+    }
+
+    @Override
     public int startOperation(IBinder token, int code, int uid, String packageName,
             boolean startIfModeDefault) {
         verifyIncomingUid(uid);
@@ -2133,8 +2334,8 @@
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             final int opCode = op.op;
-            if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+            if (uidState.hasOpMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED
                         && (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
@@ -2340,6 +2541,25 @@
         return AppOpsManager.permissionToOpCode(permission);
     }
 
+    @Override
+    public boolean shouldCollectNotes(int opCode) {
+        Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
+
+        String perm = AppOpsManager.opToPermission(opCode);
+        if (perm == null) {
+            return false;
+        }
+
+        PermissionInfo permInfo;
+        try {
+            permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+
+        return permInfo.getProtection() == PROTECTION_DANGEROUS;
+    }
+
     void finishOperationLocked(Op op, boolean finishNested) {
         final int opCode = op.op;
         final int uid = op.uidState.uid;
@@ -2434,9 +2654,8 @@
                                 || !callback.isWatchingUid(uidState.uid)) {
                             continue;
                         }
-                        boolean doAllPackages = uidState.opModes != null
-                                && uidState.opModes.indexOfKey(code) >= 0
-                                && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND;
+                        boolean doAllPackages = uidState.hasOpMode(code)
+                                && uidState.getOpMode(code) == AppOpsManager.MODE_FOREGROUND;
                         if (uidState.pkgOps != null) {
                             for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
                                 final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
@@ -2642,9 +2861,11 @@
     }
 
     private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)
-                && pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
     }
 
     private boolean isOpRestrictedLocked(int uid, int code, String packageName,
@@ -2757,12 +2978,9 @@
             if (uidState == null) {
                 continue;
             }
-            if (uidState.opModes != null) {
-                final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                        uidState.opModes.valueAt(idx));
-                }
+            if (uidState.hasOpMode(AppOpsManager.OP_RUN_IN_BACKGROUND)) {
+                uidState.putOpMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.getOpMode(
+                        AppOpsManager.OP_RUN_IN_BACKGROUND));
             }
             if (uidState.pkgOps == null) {
                 continue;
@@ -2818,10 +3036,7 @@
                 final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
                 final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
                 UidState uidState = getUidStateLocked(uid, true);
-                if (uidState.opModes == null) {
-                    uidState.opModes = new SparseIntArray();
-                }
-                uidState.opModes.put(code, mode);
+                uidState.putOpMode(code, mode);
             } else {
                 Slog.w(TAG, "Unknown element under <uid-ops>: "
                         + parser.getName());
@@ -2973,28 +3188,40 @@
                 out.startTag(null, "app-ops");
                 out.attribute(null, "v", String.valueOf(CURRENT_VERSION));
 
+                final SparseArray<SparseIntArray> uidOpModes = new SparseArray<>();
                 synchronized (this) {
-                    final int uidStateCount = mUidStates.size();
-                    for (int i = 0; i < uidStateCount; i++) {
-                        UidState uidState = mUidStates.valueAt(i);
-                        if (uidState.opModes != null && uidState.opModes.size() > 0) {
-                            out.startTag(null, "uid");
-                            out.attribute(null, "n", Integer.toString(uidState.uid));
-                            SparseIntArray uidOpModes = uidState.opModes;
-                            final int opCount = uidOpModes.size();
-                            for (int j = 0; j < opCount; j++) {
-                                final int op = uidOpModes.keyAt(j);
-                                final int mode = uidOpModes.valueAt(j);
-                                out.startTag(null, "op");
-                                out.attribute(null, "n", Integer.toString(op));
-                                out.attribute(null, "m", Integer.toString(mode));
-                                out.endTag(null, "op");
-                            }
-                            out.endTag(null, "uid");
+                    final int uidStatesSize = mUidStates.size();
+                    for (int i = 0; i < uidStatesSize; i++) {
+                        final SparseIntArray opModes = mUidStates.valueAt(i).cloneOpModes();
+                        if (opModes != null) {
+                            final int uid = mUidStates.keyAt(i);
+                            uidOpModes.put(uid, opModes);
                         }
                     }
                 }
 
+                final int uidOpModesSize = uidOpModes.size();
+                for (int uidOpModesIndex = 0; uidOpModesIndex < uidOpModesSize; uidOpModesIndex++) {
+                    final int uid = uidOpModes.keyAt(uidOpModesIndex);
+                    final SparseIntArray opModes = uidOpModes.valueAt(uidOpModesIndex);
+
+                    out.startTag(null, "uid");
+                    out.attribute(null, "n", Integer.toString(uid));
+
+                    final int opModesSize = opModes.size();
+                    for (int opModesIndex = 0; opModesIndex < opModesSize; opModesIndex++) {
+                        final int code = opModes.keyAt(opModesIndex);
+                        final int mode = opModes.valueAt(opModesIndex);
+
+                        out.startTag(null, "op");
+                        out.attribute(null, "n", Integer.toString(code));
+                        out.attribute(null, "m", Integer.toString(mode));
+                        out.endTag(null, "op");
+                    }
+
+                    out.endTag(null, "uid");
+                }
+
                 if (allOps != null) {
                     String lastPkg = null;
                     for (int i=0; i<allOps.size(); i++) {
@@ -3928,52 +4155,31 @@
                     }
                 }
             }
-            if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
-                    && dumpMode < 0 && !dumpWatchers && !dumpWatchers) {
-                boolean printedHeader = false;
-                for (int o=0; o<mAudioRestrictions.size(); o++) {
-                    final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
-                    final SparseArray<Restriction> restrictions = mAudioRestrictions.valueAt(o);
-                    for (int i=0; i<restrictions.size(); i++) {
-                        if (!printedHeader){
-                            pw.println("  Audio Restrictions:");
-                            printedHeader = true;
-                            needSep = true;
-                        }
-                        final int usage = restrictions.keyAt(i);
-                        pw.print("    "); pw.print(op);
-                        pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
-                        Restriction r = restrictions.valueAt(i);
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
-                        if (!r.exceptionPackages.isEmpty()) {
-                            pw.println("      Exceptions:");
-                            for (int j=0; j<r.exceptionPackages.size(); j++) {
-                                pw.print("        "); pw.println(r.exceptionPackages.valueAt(j));
-                            }
-                        }
-                    }
-                }
+            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
+                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers && !dumpWatchers) {
+                needSep = mAudioRestrictionManager.dump(pw) | needSep ;
             }
             if (needSep) {
                 pw.println();
             }
             for (int i=0; i<mUidStates.size(); i++) {
                 UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.opModes;
                 final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
 
                 if (dumpWatchers || dumpHistory) {
                     continue;
                 }
                 if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (uidState.opModes != null
-                            && uidState.opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasOp = dumpOp < 0 || uidState.hasOpMode(dumpOp);
                     boolean hasPackage = dumpPackage == null;
                     boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
+                    if (!hasMode) {
+                        int opModeCount = uidState.getOpModeCount();
+                        for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+                            int code = uidState.getOpCodeAt(opModeIndex);
+                            if (uidState.getOpMode(code) == dumpMode) {
                                 hasMode = true;
+                                break;
                             }
                         }
                     }
@@ -4040,20 +4246,18 @@
                 }
                 needSep = true;
 
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
+                final int opModeCount = uidState.getOpModeCount();
+                for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+                    final int code = uidState.getOpCodeAt(opModeIndex);
+                    final int mode = uidState.getOpMode(code);
+                    if (dumpOp >= 0 && dumpOp != code) {
+                        continue;
                     }
+                    if (dumpMode >= 0 && dumpMode != mode) {
+                        continue;
+                    }
+                    pw.print("      "); pw.print(AppOpsManager.opToName(code));
+                    pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
                 }
 
                 if (pkgOps == null) {
@@ -4203,12 +4407,6 @@
         }
     }
 
-    private static final class Restriction {
-        private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
-        int mode;
-        ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
-    }
-
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
         checkSystemUid("setUserRestrictions");
diff --git a/services/core/java/com/android/server/appop/AudioRestrictionManager.java b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
new file mode 100644
index 0000000..be87037
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.app.AppOpsManager;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
+import android.media.AudioAttributes;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.PrintWriter;
+
+/**
+ * AudioRestrictionManager host all audio restriction related logic and states for AppOpsService.
+ */
+public class AudioRestrictionManager {
+    static final String TAG = "AudioRestriction";
+
+    // Audio restrictions coming from Zen mode API
+    final SparseArray<SparseArray<Restriction>> mZenModeAudioRestrictions = new SparseArray<>();
+    // Audio restrictions coming from Camera2 API
+    @CAMERA_AUDIO_RESTRICTION int mCameraAudioRestriction = CameraDevice.AUDIO_RESTRICTION_NONE;
+    // Predefined <code, usages> camera audio restriction settings
+    static final SparseArray<SparseBooleanArray> CAMERA_AUDIO_RESTRICTIONS;
+
+    static {
+        SparseBooleanArray audioMutedUsages = new SparseBooleanArray();
+        SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray();
+        for (int usage : AudioAttributes.SDK_USAGES) {
+            final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
+            if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION ||
+                    suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL ||
+                    suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
+                audioMutedUsages.append(usage, true);
+                vibrationMutedUsages.append(usage, true);
+            } else if (suppressionBehavior != AudioAttributes.SUPPRESSIBLE_MEDIA &&
+                    suppressionBehavior != AudioAttributes.SUPPRESSIBLE_SYSTEM &&
+                    suppressionBehavior != AudioAttributes.SUPPRESSIBLE_NEVER) {
+                Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior);
+            }
+        }
+        CAMERA_AUDIO_RESTRICTIONS = new SparseArray<>();
+        CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages);
+        CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_VIBRATE, vibrationMutedUsages);
+    }
+
+    private static final class Restriction {
+        private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
+        int mode;
+        ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
+    }
+
+    public int checkAudioOperation(int code, int usage, int uid, String packageName) {
+        synchronized (this) {
+            // Check for camera audio restrictions
+            if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
+                if (code == AppOpsManager.OP_VIBRATE || (code == AppOpsManager.OP_PLAY_AUDIO &&
+                        mCameraAudioRestriction ==
+                                CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND)) {
+                    final SparseBooleanArray mutedUsages = CAMERA_AUDIO_RESTRICTIONS.get(code);
+                    if (mutedUsages != null) {
+                        if (mutedUsages.get(usage)) {
+                            return AppOpsManager.MODE_IGNORED;
+                        }
+                    }
+                }
+            }
+
+            final int mode = checkZenModeRestrictionLocked(code, usage, uid, packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                return mode;
+            }
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    private int checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName) {
+        final SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
+        if (usageRestrictions != null) {
+            final Restriction r = usageRestrictions.get(usage);
+            if (r != null && !r.exceptionPackages.contains(packageName)) {
+                return r.mode;
+            }
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    public void setZenModeAudioRestriction(int code, int usage, int uid, int mode,
+            String[] exceptionPackages) {
+        synchronized (this) {
+            SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
+            if (usageRestrictions == null) {
+                usageRestrictions = new SparseArray<Restriction>();
+                mZenModeAudioRestrictions.put(code, usageRestrictions);
+            }
+            usageRestrictions.remove(usage);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                final Restriction r = new Restriction();
+                r.mode = mode;
+                if (exceptionPackages != null) {
+                    final int N = exceptionPackages.length;
+                    r.exceptionPackages = new ArraySet<String>(N);
+                    for (int i = 0; i < N; i++) {
+                        final String pkg = exceptionPackages[i];
+                        if (pkg != null) {
+                            r.exceptionPackages.add(pkg.trim());
+                        }
+                    }
+                }
+                usageRestrictions.put(usage, r);
+            }
+        }
+    }
+
+    public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
+        synchronized (this) {
+            mCameraAudioRestriction = mode;
+        }
+    }
+
+    public boolean hasActiveRestrictions() {
+        boolean hasActiveRestrictions = false;
+        synchronized (this) {
+            hasActiveRestrictions = (mZenModeAudioRestrictions.size() > 0 ||
+                mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE);
+        }
+        return hasActiveRestrictions;
+    }
+
+    // return: needSep used by AppOpsService#dump
+    public boolean dump(PrintWriter pw) {
+        boolean printedHeader = false;
+        boolean needSep = hasActiveRestrictions();
+
+        synchronized (this) {
+            for (int o = 0; o < mZenModeAudioRestrictions.size(); o++) {
+                final String op = AppOpsManager.opToName(mZenModeAudioRestrictions.keyAt(o));
+                final SparseArray<Restriction> restrictions = mZenModeAudioRestrictions.valueAt(o);
+                for (int i = 0; i < restrictions.size(); i++) {
+                    if (!printedHeader){
+                        pw.println("  Zen Mode Audio Restrictions:");
+                        printedHeader = true;
+
+                    }
+                    final int usage = restrictions.keyAt(i);
+                    pw.print("    "); pw.print(op);
+                    pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
+                    Restriction r = restrictions.valueAt(i);
+                    pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
+                    if (!r.exceptionPackages.isEmpty()) {
+                        pw.println("      Exceptions:");
+                        for (int j = 0; j < r.exceptionPackages.size(); j++) {
+                            pw.print("        "); pw.println(r.exceptionPackages.valueAt(j));
+                        }
+                    }
+                }
+            }
+            if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
+                pw.println("  Camera Audio Restriction Mode: " +
+                        cameraRestrictionModeToName(mCameraAudioRestriction));
+            }
+        }
+        return needSep;
+    }
+
+    private static String cameraRestrictionModeToName(@CAMERA_AUDIO_RESTRICTION int mode) {
+        switch (mode) {
+            case CameraDevice.AUDIO_RESTRICTION_NONE:
+                return "None";
+            case CameraDevice.AUDIO_RESTRICTION_VIBRATION:
+                return "MuteVibration";
+            case CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND:
+                return "MuteVibrationAndSound";
+            default:
+                return "Unknown";
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index ace0a7d..5983785 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -28,6 +28,7 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
@@ -36,6 +37,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.LongSparseArray;
@@ -44,6 +46,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.AtomicDirectory;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
@@ -272,6 +275,10 @@
 
     void dump(String prefix, PrintWriter pw, int filterUid,
               String filterPackage, int filterOp) {
+        if (!isApiEnabled()) {
+            return;
+        }
+
         synchronized (mOnDiskLock) {
             synchronized (mInMemoryLock) {
                 pw.println();
@@ -324,6 +331,11 @@
     void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
             @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
             @OpFlags int flags, @NonNull RemoteCallback callback) {
+        if (!isApiEnabled()) {
+            callback.sendResult(new Bundle());
+            return;
+        }
+
         synchronized (mOnDiskLock) {
             synchronized (mInMemoryLock) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -344,6 +356,11 @@
     void getHistoricalOps(int uid, @NonNull String packageName,
             @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
             @OpFlags int flags, @NonNull RemoteCallback callback) {
+        if (!isApiEnabled()) {
+            callback.sendResult(new Bundle());
+            return;
+        }
+
         final long currentTimeMillis = System.currentTimeMillis();
         if (endTimeMillis == Long.MAX_VALUE) {
             endTimeMillis = currentTimeMillis;
@@ -681,6 +698,12 @@
         }
     }
 
+    private static boolean isApiEnabled() {
+        return Binder.getCallingUid() == Process.myUid()
+                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+    }
+
     private static final class Persistence {
         private static final boolean DEBUG = false;
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5bc2261..77b3fee 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -155,7 +155,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * The implementation of the volume manager service.
+ * The implementation of the audio service for volume, audio focus, device management...
  * <p>
  * This implementation focuses on delivering a responsive UI. Most methods are
  * asynchronous to external calls. For example, the task of setting a volume
@@ -845,7 +845,12 @@
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
             synchronized (mHdmiClientLock) {
+                mHdmiCecSink = false;
                 mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
+                if (mHdmiManager != null) {
+                    mHdmiManager.addHdmiControlStatusChangeListener(
+                            mHdmiControlStatusChangeListenerCallback);
+                }
                 mHdmiTvClient = mHdmiManager.getTvClient();
                 if (mHdmiTvClient != null) {
                     mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER;
@@ -856,7 +861,6 @@
                     mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
                     mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
                 }
-                mHdmiCecSink = false;
                 mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
             }
         }
@@ -1113,8 +1117,7 @@
                 checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
-                        mHdmiCecSink = false;
-                        mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
                     }
                 }
             }
@@ -1124,7 +1127,7 @@
             if (isPlatformTelevision()) {
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null) {
-                        mHdmiCecSink = false;
+                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
                     }
                 }
             }
@@ -1902,16 +1905,9 @@
                         }
                     }
 
-                    if (mHdmiAudioSystemClient != null &&
-                            mHdmiSystemAudioSupported &&
-                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                            (oldIndex != newIndex || isMuteAdjust)) {
-                        final long identity = Binder.clearCallingIdentity();
-                        mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
-                                isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
-                                getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
-                                isStreamMute(AudioSystem.STREAM_MUSIC));
-                        Binder.restoreCallingIdentity(identity);
+                    if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+                            && (oldIndex != newIndex || isMuteAdjust)) {
+                        maybeSendSystemAudioStatusCommand(isMuteAdjust);
                     }
                 }
             }
@@ -1922,12 +1918,35 @@
 
     // Called after a delay when volume down is pressed while muted
     private void onUnmuteStream(int stream, int flags) {
-        VolumeStreamState streamState = mStreamStates[stream];
-        streamState.mute(false);
+        boolean wasMuted;
+        synchronized (VolumeStreamState.class) {
+            final VolumeStreamState streamState = mStreamStates[stream];
+            wasMuted = streamState.mute(false); // if unmuting causes a change, it was muted
 
-        final int device = getDeviceForStream(stream);
-        final int index = mStreamStates[stream].getIndex(device);
-        sendVolumeUpdate(stream, index, index, flags, device);
+            final int device = getDeviceForStream(stream);
+            final int index = streamState.getIndex(device);
+            sendVolumeUpdate(stream, index, index, flags, device);
+        }
+        if (stream == AudioSystem.STREAM_MUSIC && wasMuted) {
+            synchronized (mHdmiClientLock) {
+                maybeSendSystemAudioStatusCommand(true);
+            }
+        }
+    }
+
+    @GuardedBy("mHdmiClientLock")
+    private void maybeSendSystemAudioStatusCommand(boolean isMuteAdjust) {
+        if (mHdmiAudioSystemClient == null
+                || !mHdmiSystemAudioSupported) {
+            return;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
+                isMuteAdjust, getStreamVolume(AudioSystem.STREAM_MUSIC),
+                getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
+                isStreamMute(AudioSystem.STREAM_MUSIC));
+        Binder.restoreCallingIdentity(identity);
     }
 
     private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
@@ -2340,17 +2359,9 @@
             }
         }
         synchronized (mHdmiClientLock) {
-            if (mHdmiManager != null &&
-                    mHdmiAudioSystemClient != null &&
-                    mHdmiSystemAudioSupported &&
-                    streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                    (oldIndex != index)) {
-                final long identity = Binder.clearCallingIdentity();
-                mHdmiAudioSystemClient.sendReportAudioStatusCecCommand(
-                        false, getStreamVolume(AudioSystem.STREAM_MUSIC),
-                        getStreamMaxVolume(AudioSystem.STREAM_MUSIC),
-                        isStreamMute(AudioSystem.STREAM_MUSIC));
-                Binder.restoreCallingIdentity(identity);
+            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+                    && (oldIndex != index)) {
+                maybeSendSystemAudioStatusCommand(false);
             }
         }
         sendVolumeUpdate(streamType, oldIndex, index, flags, device);
@@ -4680,7 +4691,12 @@
             }
         }
 
-        public void mute(boolean state) {
+        /**
+         * Mute/unmute the stream
+         * @param state the new mute state
+         * @return true if the mute state was changed
+         */
+        public boolean mute(boolean state) {
             boolean changed = false;
             synchronized (VolumeStreamState.class) {
                 if (state != mIsMuted) {
@@ -4705,6 +4721,7 @@
                 intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
                 sendBroadcastToAll(intent);
             }
+            return changed;
         }
 
         public int getStreamType() {
@@ -5774,31 +5791,36 @@
     //     are transformed into key events for the HDMI playback client.
     //==========================================================================================
 
-    private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback {
-        public void onComplete(int status) {
+    @GuardedBy("mHdmiClientLock")
+    private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
+        mHdmiCecSink = hdmiCecSink;
+        if (mHdmiCecSink) {
+            if (DEBUG_VOL) {
+                Log.d(TAG, "CEC sink: setting HDMI as full vol device");
+            }
+            mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+        } else {
+            if (DEBUG_VOL) {
+                Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
+            }
+            // Android TV devices without CEC service apply software volume on
+            // HDMI output
+            mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
+        }
+
+        checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
+                "HdmiPlaybackClient.DisplayStatusCallback");
+    }
+
+    private class MyHdmiControlStatusChangeListenerCallback
+            implements HdmiControlManager.HdmiControlStatusChangeListener {
+        public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
             synchronized (mHdmiClientLock) {
-                if (mHdmiManager != null) {
-                    mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN);
-                    // Television devices without CEC service apply software volume on HDMI output
-                    if (mHdmiCecSink) {
-                        if (DEBUG_VOL) {
-                            Log.d(TAG, "CEC sink: setting HDMI as full vol device");
-                        }
-                        mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
-                    } else {
-                        if (DEBUG_VOL) {
-                            Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
-                        }
-                        // Android TV devices without CEC service apply software volume on
-                        // HDMI output
-                        mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
-                    }
-                    checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI,
-                            "HdmiPlaybackClient.DisplayStatusCallback");
-                }
+                if (mHdmiManager == null) return;
+                updateHdmiCecSinkLocked(isCecEnabled ? isCecAvailable : false);
             }
         }
-    }
+    };
 
     private final Object mHdmiClientLock = new Object();
 
@@ -5815,12 +5837,14 @@
     @GuardedBy("mHdmiClientLock")
     private HdmiPlaybackClient mHdmiPlaybackClient;
     // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
+    @GuardedBy("mHdmiClientLock")
     private boolean mHdmiCecSink;
     // Set only when device is an audio system.
     @GuardedBy("mHdmiClientLock")
     private HdmiAudioSystemClient mHdmiAudioSystemClient;
 
-    private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback();
+    private MyHdmiControlStatusChangeListenerCallback mHdmiControlStatusChangeListenerCallback =
+            new MyHdmiControlStatusChangeListenerCallback();
 
     @Override
     public int setHdmiSystemAudioSupported(boolean on) {
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
new file mode 100644
index 0000000..0d34c53
--- /dev/null
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsMediaTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.cts.AudioManagerTest"
+                },
+                {
+                    "include-filter": "android.media.cts.AudioFocusTest"
+                }
+            ]
+        }
+    ]
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4af3627..7302b98 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -981,10 +981,10 @@
         mStatusBarService = mInjector.getStatusBarService();
 
         // Cache the authenticators
-        for (int i = 0; i < FEATURE_ID.length; i++) {
-            if (hasFeature(FEATURE_ID[i])) {
+        for (int featureId : FEATURE_ID) {
+            if (hasFeature(featureId)) {
                 Authenticator authenticator =
-                        new Authenticator(FEATURE_ID[i], getAuthenticator(FEATURE_ID[i]));
+                        new Authenticator(featureId, getAuthenticator(featureId));
                 mAuthenticators.add(authenticator);
             }
         }
@@ -1005,8 +1005,6 @@
      * and the error containing one of the {@link BiometricConstants} errors.
      */
     private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
-        int modality = TYPE_NONE;
-
         // No biometric features, send error
         if (mAuthenticators.isEmpty()) {
             return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
@@ -1022,10 +1020,11 @@
         boolean hasTemplatesEnrolled = false;
         boolean enabledForApps = false;
 
+        int modality = TYPE_NONE;
         int firstHwAvailable = TYPE_NONE;
-        for (int i = 0; i < mAuthenticators.size(); i++) {
-            modality = mAuthenticators.get(i).getType();
-            BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
+        for (Authenticator authenticatorWrapper : mAuthenticators) {
+            modality = authenticatorWrapper.getType();
+            BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
             if (authenticator.isHardwareDetected()) {
                 isHardwareDetected = true;
                 if (firstHwAvailable == TYPE_NONE) {
@@ -1036,9 +1035,6 @@
                 if (authenticator.hasEnrolledTemplates(userId)) {
                     hasTemplatesEnrolled = true;
                     if (isEnabledForApp(modality, userId)) {
-                        // TODO(b/110907543): When face settings (and other settings) have both a
-                        // user toggle as well as a work profile settings page, this needs to be
-                        // updated to reflect the correct setting.
                         enabledForApps = true;
                         break;
                     }
@@ -1555,7 +1551,7 @@
     }
 
     /**
-     * authenticate() (above) which is called from BiometricPrompt determines which
+     * handleAuthenticate() (above) which is called from BiometricPrompt determines which
      * modality/modalities to start authenticating with. authenticateInternal() should only be
      * used for:
      * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 8de2595..96af74a 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -36,6 +36,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -44,6 +45,9 @@
 import com.android.server.SystemService;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -75,7 +79,7 @@
     private static final int MSG_SWITCH_USER = 1;
 
     private static final int RETRY_DELAY_TIME = 20; //ms
-    private static final int RETRY_TIMES = 30;
+    private static final int RETRY_TIMES = 60;
 
     // Maximum entries to keep in usage history before dumping out
     private static final int MAX_USAGE_HISTORY = 100;
@@ -102,6 +106,9 @@
 
     private final boolean mNotifyNfc;
 
+    private ScheduledThreadPoolExecutor mLogWriterService = new ScheduledThreadPoolExecutor(
+            /*corePoolSize*/ 1);
+
     /**
      * Structure to track camera usage
      */
@@ -204,6 +211,9 @@
 
         mNotifyNfc = SystemProperties.getInt(NFC_NOTIFICATION_PROP, 0) > 0;
         if (DEBUG) Slog.v(TAG, "Notify NFC behavior is " + (mNotifyNfc ? "active" : "disabled"));
+        // Don't keep any extra logging threads if not needed
+        mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS);
+        mLogWriterService.allowCoreThreadTimeOut(true);
     }
 
     @Override
@@ -279,6 +289,51 @@
         }
     }
 
+    private class EventWriterTask implements Runnable {
+        private ArrayList<CameraUsageEvent> mEventList;
+        private static final long WRITER_SLEEP_MS = 100;
+
+        public EventWriterTask(ArrayList<CameraUsageEvent> eventList) {
+            mEventList = eventList;
+        }
+
+        @Override
+        public void run() {
+            if (mEventList != null) {
+                for (CameraUsageEvent event : mEventList) {
+                    logCameraUsageEvent(event);
+                    try {
+                        Thread.sleep(WRITER_SLEEP_MS);
+                    } catch (InterruptedException e) {}
+                }
+                mEventList.clear();
+            }
+        }
+
+        /**
+         * Write camera usage events to stats log.
+         * Package-private
+         */
+        private void logCameraUsageEvent(CameraUsageEvent e) {
+            int facing = StatsLog.CAMERA_ACTION_EVENT__FACING__UNKNOWN;
+            switch(e.mCameraFacing) {
+                case ICameraServiceProxy.CAMERA_FACING_BACK:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__BACK;
+                    break;
+                case ICameraServiceProxy.CAMERA_FACING_FRONT:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__FRONT;
+                    break;
+                case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__EXTERNAL;
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing);
+            }
+            StatsLog.write(StatsLog.CAMERA_ACTION_EVENT, e.getDuration(), e.mAPILevel,
+                    e.mClientName, facing);
+        }
+    }
+
     /**
      * Dump camera usage events to log.
      * Package-private
@@ -315,6 +370,10 @@
                         .setPackageName(e.mClientName);
                 mLogger.write(l);
             }
+
+            mLogWriterService.execute(new EventWriterTask(
+                        new ArrayList<CameraUsageEvent>(mCameraUsageHistory)));
+
             mCameraUsageHistory.clear();
         }
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 044e417..027e2fb 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -47,11 +47,10 @@
 public final class CompatConfig {
 
     private static final String TAG = "CompatConfig";
-    private static final String CONFIG_FILE_SUFFIX = "platform_compat_config.xml";
 
     private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
             Environment.buildPath(
-                    Environment.getRootDirectory(), "etc", "sysconfig"));
+                    Environment.getRootDirectory(), "etc", "compatconfig"));
 
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
@@ -212,10 +211,9 @@
             return this;
         }
         for (File f : libraryDir.listFiles()) {
+            Slog.d(TAG, "Found a config file: " + f.getPath());
             //TODO(b/138222363): Handle duplicate ids across config files.
-            if (f.getPath().endsWith(CONFIG_FILE_SUFFIX)) {
-                readConfig(f);
-            }
+            readConfig(f);
         }
         return this;
     }
@@ -223,7 +221,7 @@
     private void readConfig(File configFile) {
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
             for (Change change : XmlParser.read(in).getCompatChange()) {
-                Slog.w(TAG, "Adding: " + change.toString());
+                Slog.d(TAG, "Adding: " + change.toString());
                 addChange(new CompatChange(change));
             }
         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..81e507c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.util.Slog;
+import android.util.StatsLog;
 
+import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 
@@ -34,23 +36,27 @@
     private static final String TAG = "Compatibility";
 
     private final Context mContext;
+    private final ChangeReporter mChangeReporter;
 
     public PlatformCompat(Context context) {
         mContext = context;
+        mChangeReporter = new ChangeReporter();
     }
 
     @Override
     public void reportChange(long changeId, ApplicationInfo appInfo) {
-        Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
-        // TODO log via StatsLog
+        reportChange(changeId, appInfo, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
         if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
-            reportChange(changeId, appInfo);
+            reportChange(changeId, appInfo,
+                    StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, appInfo,
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
@@ -59,4 +65,13 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
     }
+
+    private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
+        int uid = appInfo.uid;
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 4990ea1..27f11ff 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
@@ -39,15 +41,19 @@
 
     private final Context mContext;
     private final IBatteryStats mBatteryStats;
+    private final Handler mListenerHandler;
+    private final PhoneStateListener mPhoneStateListener;
 
     private IccCardConstants.State mSimState = IccCardConstants.State.READY;
     private SignalStrength mSignalStrength;
     private ServiceState mServiceState;
     private int mDataState = TelephonyManager.DATA_DISCONNECTED;
 
-    public DataConnectionStats(Context context) {
+    public DataConnectionStats(Context context, Handler listenerHandler) {
         mContext = context;
         mBatteryStats = BatteryStatsService.getService();
+        mListenerHandler = listenerHandler;
+        mPhoneStateListener = new PhoneStateListenerImpl(listenerHandler.getLooper());
     }
 
     public void startMonitoring() {
@@ -63,7 +69,7 @@
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
-        mContext.registerReceiver(this, filter);
+        mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
     }
 
     @Override
@@ -129,7 +135,11 @@
                 && mServiceState.getState() != ServiceState.STATE_POWER_OFF;
     }
 
-    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+    private class PhoneStateListenerImpl extends PhoneStateListener {
+        PhoneStateListenerImpl(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
             mSignalStrength = signalStrength;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index faf01f9..7b3eae1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2082,7 +2082,6 @@
                     }
                     out.write(0xFF);
                     out.write(0xFF);
-                    out.flush();
 
                     // Wait for End-of-File.
                     InputStream in = mSockets[i].getInputStream();
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 36e872a..4a62bc5 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -55,6 +55,7 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -64,6 +65,8 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderDeathDispatcher;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -89,6 +92,14 @@
     /** Do a WTF if a single observer is registered more than this times. */
     private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;
 
+    /**
+     * Delay to apply to content change notifications dispatched to apps running
+     * in the background. This is used to help prevent stampeding when the user
+     * is performing CPU/RAM intensive foreground tasks, such as when capturing
+     * burst photos.
+     */
+    private static final long BACKGROUND_OBSERVER_DELAY = 10 * DateUtils.SECOND_IN_MILLIS;
+
     public static class Lifecycle extends SystemService {
         private ContentService mService;
 
@@ -426,28 +437,15 @@
                         flags, userHandle, calls);
             }
             final int numCalls = calls.size();
-            for (int i=0; i<numCalls; i++) {
-                ObserverCall oc = calls.get(i);
-                try {
-                    oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
-                    if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
-                            + uri);
-                } catch (RemoteException ex) {
-                    synchronized (mRootNode) {
-                        Log.w(TAG, "Found dead observer, removing");
-                        IBinder binder = oc.mObserver.asBinder();
-                        final ArrayList<ObserverNode.ObserverEntry> list
-                                = oc.mNode.mObservers;
-                        int numList = list.size();
-                        for (int j=0; j<numList; j++) {
-                            ObserverNode.ObserverEntry oe = list.get(j);
-                            if (oe.observer.asBinder() == binder) {
-                                list.remove(j);
-                                j--;
-                                numList--;
-                            }
-                        }
-                    }
+            for (int i = 0; i < numCalls; i++) {
+                // Immediately dispatch notifications to foreground apps that
+                // are important to the user; all other background observers are
+                // delayed to avoid stampeding
+                final ObserverCall oc = calls.get(i);
+                if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    oc.run();
+                } else {
+                    BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
                 }
             }
             if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
@@ -486,23 +484,33 @@
                 UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
     }
 
-    /**
-     * Hide this class since it is not part of api,
-     * but current unittest framework requires it to be public
-     * @hide
-     *
-     */
-    public static final class ObserverCall {
-        final ObserverNode mNode;
+    /** {@hide} */
+    @VisibleForTesting
+    public static final class ObserverCall implements Runnable {
         final IContentObserver mObserver;
         final boolean mSelfChange;
-        final int mObserverUserId;
+        final Uri mUri;
+        final int mUserId;
+        final int mProcState;
 
-        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange, int observerUserId) {
-            mNode = node;
+        ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
+                int procState) {
             mObserver = observer;
             mSelfChange = selfChange;
-            mObserverUserId = observerUserId;
+            mUri = uri;
+            mUserId = userId;
+            mProcState = procState;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mObserver.onChange(mSelfChange, mUri, mUserId);
+                if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
+            } catch (RemoteException ignored) {
+                // We already have a death observer that will clean up if the
+                // remote process dies
+            }
         }
     }
 
@@ -1345,11 +1353,8 @@
         return ContentResolver.SYNC_EXEMPTION_NONE;
     }
 
-    /**
-     * Hide this class since it is not part of api,
-     * but current unittest framework requires it to be public
-     * @hide
-     */
+    /** {@hide} */
+    @VisibleForTesting
     public static final class ObserverNode {
         private class ObserverEntry implements IBinder.DeathRecipient {
             public final IContentObserver observer;
@@ -1546,7 +1551,7 @@
             return false;
         }
 
-        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+        private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
                                               boolean observerWantsSelfNotifications, int flags,
                                               int targetUserHandle, ArrayList<ObserverCall> calls) {
             int N = mObservers.size();
@@ -1588,8 +1593,10 @@
                     if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
                             + " flags=" + Integer.toHexString(flags)
                             + " desc=" + entry.notifyForDescendants);
-                    calls.add(new ObserverCall(this, entry.observer, selfChange,
-                            UserHandle.getUserId(entry.uid)));
+                    final int procState = LocalServices.getService(ActivityManagerInternal.class)
+                            .getUidProcessState(entry.uid);
+                    calls.add(new ObserverCall(entry.observer, selfChange, uri,
+                            targetUserHandle, procState));
                 }
             }
         }
@@ -1605,14 +1612,14 @@
             if (index >= segmentCount) {
                 // This is the leaf node, notify all observers
                 if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
-                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+                collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
                         flags, targetUserHandle, calls);
             } else if (index < segmentCount){
                 segment = getUriSegment(uri, index);
                 if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
                         + segment);
                 // Notify any observers at this level who are interested in descendants
-                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+                collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
                         flags, targetUserHandle, calls);
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 58aadd1..1fc0db3 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -69,6 +69,7 @@
     private static final int MSG_ALLOWED_MODES_CHANGED = 1;
     private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
     private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
+    private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -440,23 +441,48 @@
                     mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
                             defaultPeakRefreshRate);
                     break;
+
+                case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
+                    int refreshRateInZone = msg.arg1;
+                    mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
+                            refreshRateInZone);
+                    break;
             }
         }
     }
 
     private static final class Vote {
-        // We split the app request into two priorities in case we can satisfy one desire without
-        // the other.
-        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 1;
-        public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
-        public static final int PRIORITY_LOW_BRIGHTNESS = 3;
-        public static final int PRIORITY_LOW_POWER_MODE = 4;
+        // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
+        // If the higher voters result is a range, it will fix the rate to a single choice.
+        // It's used to avoid rate switch in certain conditions.
+        public static final int PRIORITY_LOW_BRIGHTNESS = 0;
+
+        // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
+        // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
+        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+
+        // We split the app request into different priorities in case we can satisfy one desire
+        // without the other.
+
+        // Application can specify preferred refresh rate with below attrs.
+        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+        // System also forces some apps like blacklisted app to run at a lower refresh rate.
+        // @see android.R.array#config_highRefreshRateBlacklist
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+
+        // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
+        // of low priority voters. It votes [0, max(PEAK, MIN)]
+        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+
+        // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
+        public static final int PRIORITY_LOW_POWER_MODE = 5;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
         // appropriate, as well as priorityToString.
 
-        public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
+        public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
         public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
 
         /**
@@ -500,12 +526,16 @@
 
         public static String priorityToString(int priority) {
             switch (priority) {
+                case PRIORITY_LOW_BRIGHTNESS:
+                    return "PRIORITY_LOW_BRIGHTNESS";
+                case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
                     return "PRIORITY_APP_REQUEST_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_SIZE:
                     return "PRIORITY_APP_REQUEST_SIZE";
-                case PRIORITY_USER_SETTING_REFRESH_RATE:
-                    return "PRIORITY_USER_SETTING_REFRESH_RATE";
+                case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
                 case PRIORITY_LOW_POWER_MODE:
                     return "PRIORITY_LOW_POWER_MODE";
                 default:
@@ -608,12 +638,11 @@
             float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
 
-            if (peakRefreshRate < minRefreshRate) {
-                peakRefreshRate = minRefreshRate;
-            }
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+                    Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
+                    Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
 
-            Vote vote = Vote.forRefreshRates(minRefreshRate, peakRefreshRate);
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
             mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
         }
 
@@ -655,6 +684,7 @@
                 refreshRateVote = null;
                 sizeVote = null;
             }
+
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
             return;
@@ -799,6 +829,8 @@
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
+        private int mRefreshRateInZone;
+
         BrightnessObserver(Context context, Handler handler) {
             super(handler);
             mContext = context;
@@ -815,6 +847,7 @@
 
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
+
             // DeviceConfig is accessible after system ready.
             int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
             int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
@@ -824,6 +857,8 @@
                 mDisplayBrightnessThresholds = brightnessThresholds;
                 mAmbientBrightnessThresholds = ambientThresholds;
             }
+
+            mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
         }
@@ -863,8 +898,16 @@
             restartObserver();
         }
 
+        public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInZone) {
+                mRefreshRateInZone = refreshRate;
+                restartObserver();
+            }
+        }
+
         public void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
+            pw.println("    mRefreshRateInZone: " + mRefreshRateInZone);
 
             for (int d: mDisplayBrightnessThresholds) {
                 pw.println("    mDisplayBrightnessThreshold: " + d);
@@ -950,6 +993,10 @@
          * to value changes.
          */
         private boolean checkShouldObserve(int[] a) {
+            if (mRefreshRateInZone <= 0) {
+                return false;
+            }
+
             for (int d: a) {
                 if (d >= 0) {
                     return true;
@@ -959,37 +1006,42 @@
             return false;
         }
 
+        private boolean isInsideZone(int brightness, float lux) {
+            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
+                int disp = mDisplayBrightnessThresholds[i];
+                int ambi = mAmbientBrightnessThresholds[i];
+
+                if (disp >= 0 && ambi >= 0) {
+                    if (brightness <= disp && mAmbientLux <= ambi) {
+                        return true;
+                    }
+                } else if (disp >= 0) {
+                    if (brightness <= disp) {
+                        return true;
+                    }
+                } else if (ambi >= 0) {
+                    if (mAmbientLux <= ambi) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
         private void onBrightnessChangedLocked() {
             int brightness = Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS, -1);
 
             Vote vote = null;
-            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
-                int disp = mDisplayBrightnessThresholds[i];
-                int ambi = mAmbientBrightnessThresholds[i];
-
-                if (disp >= 0 && ambi >= 0) {
-                    if (brightness <= disp && mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (disp >= 0) {
-                    if (brightness <= disp) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (ambi >= 0) {
-                    if (mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                }
-
-                if (vote != null) {
-                    break;
-                }
+            boolean insideZone = isInsideZone(brightness, mAmbientLux);
+            if (insideZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
             }
 
             if (DEBUG) {
                 Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " +  mAmbientLux +
-                        (vote != null ? " 60hz only" : " no refresh rate limit"));
+                        ", Vote " + vote);
             }
             updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
         }
@@ -1104,7 +1156,6 @@
     }
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
-
         public DeviceConfigDisplaySettings() {
         }
 
@@ -1118,7 +1169,8 @@
          */
         public int[] getBrightnessThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1126,7 +1178,8 @@
          */
         public int[] getAmbientThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1143,17 +1196,32 @@
             return defaultPeakRefreshRate;
         }
 
+        public int getRefreshRateInZone() {
+            int defaultRefreshRateInZone = mContext.getResources().getInteger(
+                    R.integer.config_defaultRefreshRateInZone);
+
+            int refreshRate = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
+                    defaultRefreshRateInZone);
+
+            return refreshRate;
+        }
+
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
             int[] brightnessThresholds = getBrightnessThresholds();
             int[] ambientThresholds = getAmbientThresholds();
             Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+            int refreshRateInZone = getRefreshRateInZone();
 
             mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
                     .sendToTarget();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
                     defaultPeakRefreshRate).sendToTarget();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_ZONE_CHANGED, refreshRateInZone,
+                    0).sendToTarget();
         }
 
         private int[] getIntArrayProperty(String prop) {
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 7fb5b19..0bf43b6 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -609,6 +609,9 @@
 
                 @Override
                 public void onAnimationEnd(Animator animator) {
+                    Slog.d(TAG, tintController.getClass().getSimpleName()
+                            + " Animation cancelled: " + mIsCancelled
+                            + " to matrix: " + TintController.matrixToString(to, 16));
                     if (!mIsCancelled) {
                         // Ensure final color matrix is set at the end of the animation. If the
                         // animation is cancelled then don't set the final color matrix so the new
@@ -1314,8 +1317,10 @@
          * Reset the CCT value for the display white balance transform to its default value.
          */
         public boolean resetDisplayWhiteBalanceColorTemperature() {
-            return setDisplayWhiteBalanceColorTemperature(getContext().getResources()
-                    .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault));
+            int temperatureDefault = getContext().getResources()
+                    .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+            Slog.d(TAG, "resetDisplayWhiteBalanceColorTemperature: " + temperatureDefault);
+            return setDisplayWhiteBalanceColorTemperature(temperatureDefault);
         }
 
         /**
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index d2c6cd9..3f1c222 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -149,8 +149,6 @@
             cct = mTemperatureMax;
         }
 
-        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct);
-
         synchronized (mLock) {
             mCurrentColorTemperature = cct;
 
@@ -185,6 +183,9 @@
             java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
             java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
         }
+
+        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
+                + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
     }
 
     @Override
@@ -225,28 +226,6 @@
         }
     }
 
-    /**
-     * Format a given matrix into a string.
-     *
-     * @param matrix the matrix to format
-     * @param columns number of columns in the matrix
-     */
-    private String matrixToString(float[] matrix, int columns) {
-        if (matrix == null || columns <= 0) {
-            Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string");
-            return "";
-        }
-
-        final StringBuilder sb = new StringBuilder("");
-        for (int i = 0; i < matrix.length; i++) {
-            if (i % columns == 0) {
-                sb.append("\n      ");
-            }
-            sb.append(String.format("%9.6f", matrix[i]));
-        }
-        return sb.toString();
-    }
-
     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
         return new ColorSpace.Rgb(
                 "Display Color Space",
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index b291c64..8d8b9b2 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.util.Slog;
 
 import java.io.PrintWriter;
 
@@ -95,4 +96,29 @@
      * Returns whether or not this transform type is available on this device.
      */
     public abstract boolean isAvailable(Context context);
+
+    /**
+     * Format a given matrix into a string.
+     *
+     * @param matrix the matrix to format
+     * @param columns number of columns in the matrix
+     */
+    static String matrixToString(float[] matrix, int columns) {
+        if (matrix == null || columns <= 0) {
+            Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string,"
+                    + " matrix is null: " + (matrix == null)
+                    + " columns: " + columns);
+            return "";
+        }
+
+        final StringBuilder sb = new StringBuilder("");
+        for (int i = 0; i < matrix.length; i++) {
+            if (i % columns == 0) {
+                sb.append("\n      ");
+            }
+            sb.append(String.format("%9.6f", matrix[i]));
+        }
+        return sb.toString();
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 02ec10e..7b1f4c3 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -95,6 +95,11 @@
     // A piecewise linear relationship between high light brightness and high light bias.
     private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline;
 
+    private float mLatestAmbientColorTemperature;
+    private float mLatestAmbientBrightness;
+    private float mLatestLowLightBias;
+    private float mLatestHighLightBias;
+
     /**
      * @param brightnessSensor
      *      The sensor used to detect changes in the ambient brightness.
@@ -348,6 +353,7 @@
     public void updateAmbientColorTemperature() {
         final long time = System.currentTimeMillis();
         float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
+        mLatestAmbientColorTemperature = ambientColorTemperature;
 
         if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
             ambientColorTemperature =
@@ -355,6 +361,7 @@
         }
 
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
+        mLatestAmbientBrightness = ambientBrightness;
 
         if (ambientColorTemperature != -1.0f &&
                 mLowLightAmbientBrightnessToBiasSpline != null) {
@@ -362,6 +369,7 @@
             ambientColorTemperature =
                     bias * ambientColorTemperature + (1.0f - bias)
                     * mLowLightAmbientColorTemperature;
+            mLatestLowLightBias = bias;
         }
         if (ambientColorTemperature != -1.0f &&
                 mHighLightAmbientBrightnessToBiasSpline != null) {
@@ -369,6 +377,7 @@
             ambientColorTemperature =
                     (1.0f - bias) * ambientColorTemperature + bias
                     * mHighLightAmbientColorTemperature;
+            mLatestHighLightBias = bias;
         }
 
         if (mAmbientColorTemperatureOverride != -1.0f) {
@@ -426,6 +435,11 @@
         }
         mPendingAmbientColorTemperature = -1.0f;
         mAmbientColorTemperatureHistory.add(mAmbientColorTemperature);
+        Slog.d(TAG, "Display cct: " + mAmbientColorTemperature
+                + " Latest ambient cct: " + mLatestAmbientColorTemperature
+                + " Latest ambient lux: " + mLatestAmbientBrightness
+                + " Latest low light bias: " + mLatestLowLightBias
+                + " Latest high light bias: " + mLatestHighLightBias);
         mColorDisplayServiceInternal.setDisplayWhiteBalanceColorTemperature(
                 (int) mAmbientColorTemperature);
         mLastAmbientColorTemperature = mAmbientColorTemperature;
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 376a864..c2af29c 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -32,10 +32,12 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.Xml;
+
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -562,7 +564,7 @@
 
         @Override
         public void onEvent(int event, String path) {
-            if (path.endsWith(".xml")) {
+            if (path != null && path.endsWith(".xml")) {
                 // we wait 250ms before taking any action on an event, in order to dedup multiple
                 // events. E.g. a delete event followed by a create event followed by a subsequent
                 // write+close event
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index dacf372..9a85f952 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1635,6 +1635,10 @@
         removeAction(SystemAudioAutoInitiationAction.class);
         removeAction(SystemAudioStatusAction.class);
         removeAction(VolumeControlAction.class);
+
+        if (!mService.isControlEnabled()) {
+            setSystemAudioMode(false);
+        }
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 4d5dc6a..3856de4 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -42,6 +42,7 @@
 import android.hardware.hdmi.HdmiPortInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiControlStatusChangeListener;
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
@@ -252,6 +253,11 @@
     // Type of logical devices hosted in the system. Stored in the unmodifiable list.
     private final List<Integer> mLocalDevices;
 
+    // List of records for HDMI control status change listener for death monitoring.
+    @GuardedBy("mLock")
+    private final ArrayList<HdmiControlStatusChangeListenerRecord>
+            mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
+
     // List of records for hotplug event listener to handle the the caller killed in action.
     @GuardedBy("mLock")
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
@@ -297,6 +303,11 @@
             mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
 
     @Nullable
+    // Save callback when the device is still under logcial address allocation
+    // Invoke once new local device is ready.
+    private IHdmiControlCallback mDisplayStatusCallback = null;
+
+    @Nullable
     private HdmiCecController mCecController;
 
     // HDMI port information. Stored in the unmodifiable list to keep the static information
@@ -576,6 +587,7 @@
         }
         if (reason != -1) {
             invokeVendorCommandListenersOnControlStateChanged(true, reason);
+            announceHdmiControlStatusChange(true);
         }
     }
 
@@ -763,6 +775,11 @@
                     // Address allocation completed for all devices. Notify each device.
                     if (allocatingDevices.size() == ++finished[0]) {
                         mAddressAllocated = true;
+                        // Reinvoke the saved display status callback once the local device is ready.
+                        if (mDisplayStatusCallback != null) {
+                            queryDisplayStatus(mDisplayStatusCallback);
+                            mDisplayStatusCallback = null;
+                        }
                         if (initiatedBy != INITIATED_BY_HOTPLUG) {
                             // In case of the hotplug we don't call onInitializeCecComplete()
                             // since we reallocate the logical address only.
@@ -1360,6 +1377,37 @@
 
     // Record class that monitors the event of the caller of being killed. Used to clean up
     // the listener list and record list accordingly.
+    private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient {
+        private final IHdmiControlStatusChangeListener mListener;
+
+        HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mLock) {
+                mHdmiControlStatusChangeListenerRecords.remove(this);
+            }
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false;
+            if (obj == this) return true;
+            HdmiControlStatusChangeListenerRecord other =
+                    (HdmiControlStatusChangeListenerRecord) obj;
+            return other.mListener == this.mListener;
+        }
+
+        @Override
+        public int hashCode() {
+            return mListener.hashCode();
+        }
+    }
+
+    // Record class that monitors the event of the caller of being killed. Used to clean up
+    // the listener list and record list accordingly.
     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
         private final IHdmiHotplugEventListener mListener;
 
@@ -1685,6 +1733,20 @@
         }
 
         @Override
+        public void addHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.addHdmiControlStatusChangeListener(listener);
+        }
+
+        @Override
+        public void removeHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+            enforceAccessPermission();
+            HdmiControlService.this.removeHdmiControlStatusChangeListener(listener);
+        }
+
+        @Override
         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
             enforceAccessPermission();
             HdmiControlService.this.addHotplugEventListener(listener);
@@ -2192,6 +2254,13 @@
     @ServiceThreadOnly
     private void queryDisplayStatus(final IHdmiControlCallback callback) {
         assertRunOnServiceThread();
+        if (!mAddressAllocated) {
+            mDisplayStatusCallback = callback;
+            Slog.d(TAG, "Local device is under address allocation. "
+                        + "Queue display callback for later process.");
+            return;
+        }
+
         HdmiCecLocalDevicePlayback source = playback();
         if (source == null) {
             Slog.w(TAG, "Local playback device not available");
@@ -2201,6 +2270,51 @@
         source.queryDisplayStatus(callback);
     }
 
+    private void addHdmiControlStatusChangeListener(
+            final IHdmiControlStatusChangeListener listener) {
+        final HdmiControlStatusChangeListenerRecord record =
+                new HdmiControlStatusChangeListenerRecord(listener);
+        try {
+            listener.asBinder().linkToDeath(record, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Listener already died");
+            return;
+        }
+        synchronized (mLock) {
+            mHdmiControlStatusChangeListenerRecords.add(record);
+        }
+
+        // Inform the listener of the initial state of each HDMI port by generating
+        // hotplug events.
+        runOnServiceThread(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mLock) {
+                    if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
+                }
+
+                // Return the current status of mHdmiControlEnabled;
+                synchronized (mLock) {
+                    invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
+                }
+            }
+        });
+    }
+
+    private void removeHdmiControlStatusChangeListener(
+            final IHdmiControlStatusChangeListener listener) {
+        synchronized (mLock) {
+            for (HdmiControlStatusChangeListenerRecord record :
+                    mHdmiControlStatusChangeListenerRecords) {
+                if (record.mListener.asBinder() == listener.asBinder()) {
+                    listener.asBinder().unlinkToDeath(record, 0);
+                    mHdmiControlStatusChangeListenerRecords.remove(record);
+                    break;
+                }
+            }
+        }
+    }
+
     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
         try {
@@ -2433,6 +2547,47 @@
         }
     }
 
+    private void announceHdmiControlStatusChange(boolean isEnabled) {
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            for (HdmiControlStatusChangeListenerRecord record :
+                    mHdmiControlStatusChangeListenerRecords) {
+                invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
+            }
+        }
+    }
+
+    private void invokeHdmiControlStatusChangeListenerLocked(
+            IHdmiControlStatusChangeListener listener, boolean isEnabled) {
+        if (isEnabled) {
+            queryDisplayStatus(new IHdmiControlCallback.Stub() {
+                public void onComplete(int status) {
+                    boolean isAvailable = true;
+                    if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
+                            || status == HdmiControlManager.RESULT_EXCEPTION
+                            || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
+                        isAvailable = false;
+                    }
+
+                    try {
+                        listener.onStatusChange(isEnabled, isAvailable);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+                                + " isAvailable: " + isAvailable, e);
+                    }
+                }
+            });
+            return;
+        }
+
+        try {
+            listener.onStatusChange(isEnabled, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
+                    + " isAvailable: " + false, e);
+        }
+    }
+
     public HdmiCecLocalDeviceTv tv() {
         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
     }
@@ -2802,6 +2957,8 @@
                 disableHdmiControlService();
             }
         });
+        announceHdmiControlStatusChange(enabled);
+
         return;
     }
 
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 9782f30..d71ffb7 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -48,6 +48,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -79,7 +80,7 @@
         S extends AbstractPerUserSystemService<S, M>> extends SystemService {
 
     /** On a package update, does not refresh the per-user service in the cache. */
-    public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0;
+    public static final int PACKAGE_UPDATE_POLICY_NO_REFRESH = 0x00000001;
 
     /**
      * On a package update, removes any existing per-user services in the cache.
@@ -87,20 +88,40 @@
      * <p>This does not immediately recreate these services. It is assumed they will be recreated
      * for the next user request.
      */
-    public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 1;
+    public static final int PACKAGE_UPDATE_POLICY_REFRESH_LAZY = 0x00000002;
 
     /**
      * On a package update, removes and recreates any existing per-user services in the cache.
      */
-    public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 2;
+    public static final int PACKAGE_UPDATE_POLICY_REFRESH_EAGER = 0x00000004;
 
-    @IntDef(flag = true, prefix = { "PACKAGE_UPDATE_POLICY_" }, value = {
+    /** On a package restart, does not refresh the per-user service in the cache. */
+    public static final int PACKAGE_RESTART_POLICY_NO_REFRESH = 0x00000010;
+
+    /**
+     * On a package restart, removes any existing per-user services in the cache.
+     *
+     * <p>This does not immediately recreate these services. It is assumed they will be recreated
+     * for the next user request.
+     */
+    public static final int PACKAGE_RESTART_POLICY_REFRESH_LAZY = 0x00000020;
+
+    /**
+     * On a package restart, removes and recreates any existing per-user services in the cache.
+     */
+    public static final int PACKAGE_RESTART_POLICY_REFRESH_EAGER = 0x00000040;
+
+    @IntDef(flag = true, prefix = { "PACKAGE_" }, value = {
             PACKAGE_UPDATE_POLICY_NO_REFRESH,
             PACKAGE_UPDATE_POLICY_REFRESH_LAZY,
-            PACKAGE_UPDATE_POLICY_REFRESH_EAGER
+            PACKAGE_UPDATE_POLICY_REFRESH_EAGER,
+            PACKAGE_RESTART_POLICY_NO_REFRESH,
+            PACKAGE_RESTART_POLICY_REFRESH_LAZY,
+            PACKAGE_RESTART_POLICY_REFRESH_EAGER
     })
+
     @Retention(RetentionPolicy.SOURCE)
-    public @interface PackageUpdatePolicy {}
+    public @interface ServicePackagePolicyFlags {}
 
     /**
      * Log tag
@@ -153,12 +174,10 @@
     private final SparseArray<S> mServicesCache = new SparseArray<>();
 
     /**
-     * Whether the per-user service should be removed from the cache when its apk is updated.
-     *
-     * <p>One of {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
-     * {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY} or {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}.
+     * Value that determines whether the per-user service should be removed from the cache when its
+     * apk is updated or restarted.
      */
-    private final @PackageUpdatePolicy int mPackageUpdatePolicy;
+    private final @ServicePackagePolicyFlags int mServicePackagePolicyFlags;
 
     /**
      * Name of the service packages whose APK are being updated, keyed by user id.
@@ -167,6 +186,12 @@
     private SparseArray<String> mUpdatingPackageNames;
 
     /**
+     * Lazy-loadable reference to {@link UserManagerInternal}.
+     */
+    @Nullable
+    private UserManagerInternal mUm;
+
+    /**
      * Default constructor.
      *
      * <p>When using this constructor, the {@link AbstractPerUserSystemService} is removed from
@@ -184,11 +209,11 @@
             @Nullable ServiceNameResolver serviceNameResolver,
             @Nullable String disallowProperty) {
         this(context, serviceNameResolver, disallowProperty,
-                /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_REFRESH_LAZY);
+                PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_LAZY);
     }
 
     /**
-     * Full constructor.
+     * Full Constructor.
      *
      * @param context system context.
      * @param serviceNameResolver resolver for
@@ -197,19 +222,32 @@
      * @param disallowProperty when not {@code null}, defines a {@link UserManager} restriction that
      *        disables the service. <b>NOTE: </b> you'll also need to add it to
      *        {@code UserRestrictionsUtils.USER_RESTRICTIONS}.
-     * @param packageUpdatePolicy when {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY}, the
-     *        {@link AbstractPerUserSystemService} is removed from the cache when the service
-     *        package is updated; when {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER}, the
-     *        {@link AbstractPerUserSystemService} is removed from the cache and immediately
-     *        re-added when the service package is updated; when
-     *        {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH}, the service is untouched during the update.
+     * @param servicePackagePolicyFlags a combination of
+     *        {@link #PACKAGE_UPDATE_POLICY_NO_REFRESH},
+     *        {@link #PACKAGE_UPDATE_POLICY_REFRESH_LAZY},
+     *        {@link #PACKAGE_UPDATE_POLICY_REFRESH_EAGER},
+     *        {@link #PACKAGE_RESTART_POLICY_NO_REFRESH},
+     *        {@link #PACKAGE_RESTART_POLICY_REFRESH_LAZY} or
+     *        {@link #PACKAGE_RESTART_POLICY_REFRESH_EAGER}
      */
     protected AbstractMasterSystemService(@NonNull Context context,
-            @Nullable ServiceNameResolver serviceNameResolver,
-            @Nullable String disallowProperty, @PackageUpdatePolicy int packageUpdatePolicy) {
+            @Nullable ServiceNameResolver serviceNameResolver, @Nullable String disallowProperty,
+            @ServicePackagePolicyFlags int servicePackagePolicyFlags) {
         super(context);
 
-        mPackageUpdatePolicy = packageUpdatePolicy;
+        final int updatePolicyMask = PACKAGE_UPDATE_POLICY_NO_REFRESH
+                | PACKAGE_UPDATE_POLICY_REFRESH_LAZY | PACKAGE_UPDATE_POLICY_REFRESH_EAGER;
+        if ((servicePackagePolicyFlags & updatePolicyMask) == 0) {
+            // If the package update policy is not set, add the default flag
+            servicePackagePolicyFlags |= PACKAGE_UPDATE_POLICY_REFRESH_LAZY;
+        }
+        final int restartPolicyMask = PACKAGE_RESTART_POLICY_NO_REFRESH
+                | PACKAGE_RESTART_POLICY_REFRESH_LAZY | PACKAGE_RESTART_POLICY_REFRESH_EAGER;
+        if ((servicePackagePolicyFlags & restartPolicyMask) == 0) {
+            // If the package restart policy is not set, add the default flag
+            servicePackagePolicyFlags |= PACKAGE_RESTART_POLICY_REFRESH_LAZY;
+        }
+        mServicePackagePolicyFlags = servicePackagePolicyFlags;
 
         mServiceNameResolver = serviceNameResolver;
         if (mServiceNameResolver != null) {
@@ -222,9 +260,8 @@
         } else {
             mDisabledByUserRestriction = new SparseBooleanArray();
             // Hookup with UserManager to disable service when necessary.
-            final UserManager um = context.getSystemService(UserManager.class);
-            final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
-            final List<UserInfo> users = um.getUsers();
+            final UserManagerInternal umi = getUserManagerInternal();
+            final List<UserInfo> users = getSupportedUsers();
             for (int i = 0; i < users.size(); i++) {
                 final int userId = users.get(i).id;
                 final boolean disabled = umi.getUserRestriction(userId, disallowProperty);
@@ -606,6 +643,20 @@
     }
 
     /**
+     * Called after the package data that provides the service for the given user is cleared.
+     */
+    protected void onServicePackageDataClearedLocked(@UserIdInt int userId) {
+        if (verbose) Slog.v(mTag, "onServicePackageDataCleared(" + userId + ")");
+    }
+
+    /**
+     * Called after the package that provides the service for the given user is restarted.
+     */
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        if (verbose) Slog.v(mTag, "onServicePackageRestarted(" + userId + ")");
+    }
+
+    /**
      * Called after the service is removed from the cache.
      */
     @SuppressWarnings("unused")
@@ -649,6 +700,36 @@
     }
 
     /**
+     * Gets a cached reference to {@link UserManagerInternal}.
+     */
+    @NonNull
+    protected UserManagerInternal getUserManagerInternal() {
+        if (mUm == null) {
+            if (verbose) Slog.v(mTag, "lazy-loading UserManagerInternal");
+            mUm = LocalServices.getService(UserManagerInternal.class);
+        }
+        return mUm;
+    }
+
+    /**
+     * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
+     * check).
+     */
+    @NonNull
+    protected List<UserInfo> getSupportedUsers() {
+        final UserInfo[] allUsers = getUserManagerInternal().getUserInfos();
+        final int size = allUsers.length;
+        final List<UserInfo> supportedUsers = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            final UserInfo userInfo = allUsers[i];
+            if (isSupported(userInfo)) {
+                supportedUsers.add(userInfo);
+            }
+        }
+        return supportedUsers;
+    }
+
+    /**
      * Asserts that the given package name is owned by the UID making this call.
      *
      * @throws SecurityException when it's not...
@@ -677,15 +758,14 @@
             final int size = mServicesCache.size();
             pw.print(prefix); pw.print("Debug: "); pw.print(realDebug);
             pw.print(" Verbose: "); pw.println(realVerbose);
-            pw.print("Refresh on package update: "); pw.println(mPackageUpdatePolicy);
+            pw.print("Package policy flags: "); pw.println(mServicePackagePolicyFlags);
             if (mUpdatingPackageNames != null) {
                 pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
             }
             if (mServiceNameResolver != null) {
                 pw.print(prefix); pw.print("Name resolver: ");
                 mServiceNameResolver.dumpShort(pw); pw.println();
-                final UserManager um = getContext().getSystemService(UserManager.class);
-                final List<UserInfo> users = um.getUsers();
+                final List<UserInfo> users = getSupportedUsers();
                 for (int i = 0; i < users.size(); i++) {
                     final int userId = users.get(i).id;
                     pw.print(prefix2); pw.print(userId); pw.print(": ");
@@ -733,7 +813,12 @@
                     }
                     mUpdatingPackageNames.put(userId, packageName);
                     onServicePackageUpdatingLocked(userId);
-                    if (mPackageUpdatePolicy != PACKAGE_UPDATE_POLICY_NO_REFRESH) {
+                    if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_NO_REFRESH) != 0) {
+                        if (debug) {
+                            Slog.d(mTag, "Holding service for user " + userId + " while package "
+                                    + activePackageName + " is being updated");
+                        }
+                    } else {
                         if (debug) {
                             Slog.d(mTag, "Removing service for user " + userId
                                     + " because package " + activePackageName
@@ -741,18 +826,14 @@
                         }
                         removeCachedServiceLocked(userId);
 
-                        if (mPackageUpdatePolicy == PACKAGE_UPDATE_POLICY_REFRESH_EAGER) {
+                        if ((mServicePackagePolicyFlags & PACKAGE_UPDATE_POLICY_REFRESH_EAGER)
+                                != 0) {
                             if (debug) {
                                 Slog.d(mTag, "Eagerly recreating service for user "
                                         + userId);
                             }
                             getServiceForUserLocked(userId);
                         }
-                    } else {
-                        if (debug) {
-                            Slog.d(mTag, "Holding service for user " + userId + " while package "
-                                    + activePackageName + " is being updated");
-                        }
                     }
                 }
             }
@@ -804,7 +885,13 @@
                             if (!doit) {
                                 return true;
                             }
-                            removeCachedServiceLocked(getChangingUserId());
+                            final String action = intent.getAction();
+                            final int userId = getChangingUserId();
+                            if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
+                                handleActiveServiceRestartedLocked(activePackageName, userId);
+                            } else {
+                                removeCachedServiceLocked(userId);
+                            }
                         } else {
                             handlePackageUpdateLocked(pkg);
                         }
@@ -813,6 +900,23 @@
                 return false;
             }
 
+            @Override
+            public void onPackageDataCleared(String packageName, int uid) {
+                if (verbose) Slog.v(mTag, "onPackageDataCleared(): " + packageName);
+                final int userId = getChangingUserId();
+                synchronized (mLock) {
+                    final S service = peekServiceForUserLocked(userId);
+                    if (service != null) {
+                        final ComponentName componentName = service.getServiceComponentName();
+                        if (componentName != null) {
+                            if (packageName.equals(componentName.getPackageName())) {
+                                onServicePackageDataClearedLocked(userId);
+                            }
+                        }
+                    }
+                }
+            }
+
             private void handleActiveServiceRemoved(@UserIdInt int userId) {
                 synchronized (mLock) {
                     removeCachedServiceLocked(userId);
@@ -824,6 +928,31 @@
                 }
             }
 
+            private void handleActiveServiceRestartedLocked(String activePackageName,
+                    @UserIdInt int userId) {
+                if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_NO_REFRESH) != 0) {
+                    if (debug) {
+                        Slog.d(mTag, "Holding service for user " + userId + " while package "
+                                + activePackageName + " is being restarted");
+                    }
+                } else {
+                    if (debug) {
+                        Slog.d(mTag, "Removing service for user " + userId
+                                + " because package " + activePackageName
+                                + " is being restarted");
+                    }
+                    removeCachedServiceLocked(userId);
+
+                    if ((mServicePackagePolicyFlags & PACKAGE_RESTART_POLICY_REFRESH_EAGER) != 0) {
+                        if (debug) {
+                            Slog.d(mTag, "Eagerly recreating service for user " + userId);
+                        }
+                        getServiceForUserLocked(userId);
+                    }
+                }
+                onServicePackageRestartedLocked(userId);
+            }
+
             private String getActiveServicePackageNameLocked() {
                 final int userId = getChangingUserId();
                 final S service = peekServiceForUserLocked(userId);
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
new file mode 100644
index 0000000..019aa4f
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -0,0 +1,6 @@
+omernebil@google.com
+khelmy@google.com
+mdchurchill@google.com
+sturla@google.com
+songpan@google.com
+bjy@google.com
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index c1a6394..ccfc98e 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.location.Location;
-import android.location.LocationProvider;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.WorkSource;
 
@@ -81,7 +81,12 @@
      * any thread.
      */
     protected void setEnabled(boolean enabled) {
-        mLocationProviderManager.onSetEnabled(enabled);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mLocationProviderManager.onSetEnabled(enabled);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
@@ -89,21 +94,36 @@
      * any thread.
      */
     protected void setProperties(ProviderProperties properties) {
-        mLocationProviderManager.onSetProperties(properties);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mLocationProviderManager.onSetProperties(properties);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Call this method to report a new location. May be called from any thread.
      */
     protected void reportLocation(Location location) {
-        mLocationProviderManager.onReportLocation(location);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mLocationProviderManager.onReportLocation(location);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
      * Call this method to report a new location. May be called from any thread.
      */
     protected void reportLocation(List<Location> locations) {
-        mLocationProviderManager.onReportLocation(locations);
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mLocationProviderManager.onReportLocation(locations);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     /**
@@ -132,26 +152,4 @@
      * thread.
      */
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
-
-    /**
-     * Invoked by the location service to retrieve the current status of the provider. May be
-     * invoked from any thread.
-     *
-     * @deprecated Will be removed in a future release.
-     */
-    @Deprecated
-    public int getStatus(Bundle extras) {
-        return LocationProvider.AVAILABLE;
-    }
-
-    /**
-     * Invoked by the location service to retrieve the last update time of the status of the
-     * provider. May be invoked from any thread.
-     *
-     * @deprecated Will be removed in a future release.
-     */
-    @Deprecated
-    public long getStatusUpdateTime() {
-        return 0;
-    }
 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 461f19b..c6226fa 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -37,7 +37,6 @@
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
-import android.location.LocationProvider;
 import android.location.LocationRequest;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
@@ -279,15 +278,6 @@
 
     private final Object mLock = new Object();
 
-    // current status
-    private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
-
-    // time for last status update
-    private long mStatusUpdateTime = SystemClock.elapsedRealtime();
-
-    // turn off GPS fix icon if we haven't received a fix in 10 seconds
-    private static final long RECENT_FIX_TIMEOUT = 10 * 1000;
-
     // stop trying if we do not receive a fix within 60 seconds
     private static final int NO_FIX_TIMEOUT = 60 * 1000;
 
@@ -485,7 +475,7 @@
                     break;
                 case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
                 case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
-                    subscriptionOrCarrierConfigChanged(context);
+                    subscriptionOrCarrierConfigChanged();
                     break;
             }
         }
@@ -500,7 +490,7 @@
         mGnssMetrics.resetConstellationTypes();
     }
 
-    private void subscriptionOrCarrierConfigChanged(Context context) {
+    private void subscriptionOrCarrierConfigChanged() {
         if (DEBUG) Log.d(TAG, "received SIM related action: ");
         TelephonyManager phone = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -1010,24 +1000,6 @@
     }
 
     @Override
-    public int getStatus(Bundle extras) {
-        mLocationExtras.setBundle(extras);
-        return mStatus;
-    }
-
-    private void updateStatus(int status) {
-        if (status != mStatus) {
-            mStatus = status;
-            mStatusUpdateTime = SystemClock.elapsedRealtime();
-        }
-    }
-
-    @Override
-    public long getStatusUpdateTime() {
-        return mStatusUpdateTime;
-    }
-
-    @Override
     public void onSetRequest(ProviderRequest request, WorkSource source) {
         sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
     }
@@ -1266,7 +1238,6 @@
             }
 
             // reset SV count to zero
-            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
             mLocationExtras.reset();
             mFixRequestTime = SystemClock.elapsedRealtime();
             if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) {
@@ -1290,7 +1261,6 @@
             mLastPositionMode = null;
 
             // reset SV count to zero
-            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
             mLocationExtras.reset();
         }
     }
@@ -1381,7 +1351,7 @@
             mGnssStatusListenerHelper.onFirstFix(mTimeToFirstFix);
         }
 
-        if (mStarted && mStatus != LocationProvider.AVAILABLE) {
+        if (mStarted) {
             // For devices that use framework scheduling, a timer may be set to ensure we don't
             // spend too much power searching for a location, when the requested update rate is
             // slow.
@@ -1389,8 +1359,6 @@
             if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) {
                 mAlarmManager.cancel(mTimeoutIntent);
             }
-
-            updateStatus(LocationProvider.AVAILABLE);
         }
 
         if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted &&
@@ -1458,7 +1426,7 @@
                 info.mSvCarrierFreqs);
 
         // Log CN0 as part of GNSS metrics
-        mGnssMetrics.logCn0(info.mCn0s, info.mSvCount);
+        mGnssMetrics.logCn0(info.mCn0s, info.mSvCount, info.mSvCarrierFreqs);
 
         if (VERBOSE) {
             Log.v(TAG, "SV count: " + info.mSvCount);
@@ -1505,10 +1473,7 @@
         // return number of sats used in fix instead of total reported
         mLocationExtras.set(usedInFixCount, meanCn0, maxCn0);
 
-        if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
-                SystemClock.elapsedRealtime() - mLastFixTime > RECENT_FIX_TIMEOUT) {
-            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
-        }
+        mGnssMetrics.logSvStatus(info.mSvCount, info.mSvidWithFlags, info.mSvCarrierFreqs);
     }
 
     @NativeEntryPoint
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 09911ff..694f149 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.location.Location;
-import android.location.LocationProvider;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -190,22 +189,6 @@
     }
 
     @Override
-    public int getStatus(Bundle extras) {
-        return mServiceWatcher.runOnBinderBlocking(binder -> {
-            ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            return service.getStatus(extras);
-        }, LocationProvider.TEMPORARILY_UNAVAILABLE);
-    }
-
-    @Override
-    public long getStatusUpdateTime() {
-        return mServiceWatcher.runOnBinderBlocking(binder -> {
-            ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            return service.getStatusUpdateTime();
-        }, 0L);
-    }
-
-    @Override
     public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index b0c4c2e..472876b 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -19,8 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
-import android.location.LocationProvider;
-import android.os.Bundle;
 import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
@@ -38,9 +36,6 @@
 
     private boolean mEnabled;
     @Nullable private Location mLocation;
-    private int mStatus;
-    private long mStatusUpdateTime;
-    private Bundle mExtras;
 
     public MockProvider(Context context,
             LocationProviderManager locationProviderManager, ProviderProperties properties) {
@@ -48,9 +43,6 @@
 
         mEnabled = true;
         mLocation = null;
-        mStatus = LocationProvider.AVAILABLE;
-        mStatusUpdateTime = 0;
-        mExtras = null;
 
         setProperties(properties);
     }
@@ -72,13 +64,6 @@
         }
     }
 
-    /** Sets the status for this mock provider. */
-    public void setStatus(int status, Bundle extras, long updateTime) {
-        mStatus = status;
-        mStatusUpdateTime = updateTime;
-        mExtras = extras;
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("last location=" + mLocation);
@@ -86,19 +71,4 @@
 
     @Override
     public void onSetRequest(ProviderRequest request, WorkSource source) {}
-
-    @Override
-    public int getStatus(Bundle extras) {
-        if (mExtras != null) {
-            extras.clear();
-            extras.putAll(mExtras);
-        }
-
-        return mStatus;
-    }
-
-    @Override
-    public long getStatusUpdateTime() {
-        return mStatusUpdateTime;
-    }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f38f2f9..b7eca29 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -217,6 +217,11 @@
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
 
     private boolean mFirstCallToVold;
+    // Current password metric for all users on the device. Updated when user unlocks
+    // the device or changes password. Removed when user is stopped.
+    @GuardedBy("this")
+    final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
+
     protected IGateKeeperService mGateKeeperService;
     protected IAuthSecret mAuthSecretService;
 
@@ -424,6 +429,13 @@
             return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         }
 
+        /**
+         * Return the {@link DevicePolicyManager} object.
+         *
+         * Since LockSettingsService is considered a lower-level component than DevicePolicyManager,
+         * do NOT hold any lock in this class while calling into DevicePolicyManager to prevent
+         * the risk of deadlock.
+         */
         public DevicePolicyManager getDevicePolicyManager() {
             return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
@@ -593,6 +605,9 @@
         // the device.
         int strongAuthRequired = LockPatternUtils.StrongAuthTracker.getDefaultFlags(mContext);
         requireStrongAuth(strongAuthRequired, userId);
+        synchronized (this) {
+            mUserPasswordMetrics.remove(userId);
+        }
     }
 
     public void onStartUser(final int userId) {
@@ -1446,7 +1461,7 @@
     // This method should be called by LockPatternUtil only, all internal methods in this class
     // should call setLockCredentialInternal.
     @Override
-    public void setLockCredential(byte[] credential, int type,
+    public boolean setLockCredential(byte[] credential, int type,
             byte[] savedCredential, int requestedQuality, int userId,
             boolean allowUntrustedChange) {
 
@@ -1456,8 +1471,10 @@
         }
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
-            setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId,
-                    allowUntrustedChange, /* isLockTiedToParent= */ false);
+            if (!setLockCredentialInternal(credential, type, savedCredential, requestedQuality,
+                    userId, allowUntrustedChange, /* isLockTiedToParent= */ false)) {
+                return false;
+            }
             setSeparateProfileChallengeEnabledLocked(userId, true, null);
             notifyPasswordChanged(userId);
         }
@@ -1466,13 +1483,14 @@
             setDeviceUnlockedForUser(userId);
         }
         notifySeparateProfileChallengeChanged(userId);
+        return true;
     }
 
     /**
      * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
      *     credentials are being tied to its parent's credentials.
      */
-    private void setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
+    private boolean setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
             byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange,
             boolean isLockTiedToParent) {
         // Normalize savedCredential and credential such that empty string is always represented
@@ -1485,9 +1503,9 @@
         }
         synchronized (mSpManager) {
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
-                return;
+                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
+                        savedCredential, requestedQuality, userId, allowUntrustedChange,
+                        isLockTiedToParent);
             }
         }
 
@@ -1501,10 +1519,10 @@
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            notifyActivePasswordMetricsAvailable(CREDENTIAL_TYPE_NONE, null, userId);
+            setUserPasswordMetrics(CREDENTIAL_TYPE_NONE, null, userId);
             sendCredentialsOnChangeIfRequired(
                     credentialType, credential, userId, isLockTiedToParent);
-            return;
+            return true;
         }
         if (credential == null) {
             throw new IllegalArgumentException("Null credential with mismatched credential type");
@@ -1537,37 +1555,38 @@
             if (shouldMigrateToSyntheticPasswordLocked(userId)) {
                 initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
                         currentHandle.type, requestedQuality, userId);
-                spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
-                return;
+                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
+                        savedCredential, requestedQuality, userId, allowUntrustedChange,
+                        isLockTiedToParent);
             }
         }
         if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
         byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
                 userId);
-        if (enrolledHandle != null) {
-            CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
-            mStorage.writeCredentialHash(willStore, userId);
-            // push new secret and auth token to vold
-            GateKeeperResponse gkResponse;
-            try {
-                gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
-                        credential);
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Failed to verify current credential", e);
-            }
-            setUserKeyProtection(userId, credential, convertResponse(gkResponse));
-            fixateNewestUserKeyAuth(userId);
-            // Refresh the auth token
-            doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
-                    null /* progressCallback */);
-            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            sendCredentialsOnChangeIfRequired(
-                    credentialType, credential, userId, isLockTiedToParent);
-        } else {
-            throw new IllegalStateException(String.format("Failed to enroll %s",
+        if (enrolledHandle == null) {
+            Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
                     credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+            return false;
         }
+        CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
+        mStorage.writeCredentialHash(willStore, userId);
+        // push new secret and auth token to vold
+        GateKeeperResponse gkResponse;
+        try {
+            gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
+                    credential);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to verify current credential", e);
+        }
+        setUserKeyProtection(userId, credential, convertResponse(gkResponse));
+        fixateNewestUserKeyAuth(userId);
+        // Refresh the auth token
+        doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
+                null /* progressCallback */);
+        synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+        sendCredentialsOnChangeIfRequired(
+                credentialType, credential, userId, isLockTiedToParent);
+        return true;
     }
 
     private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
@@ -1946,7 +1965,7 @@
                     Log.w(TAG, "progressCallback throws exception", e);
                 }
             }
-            notifyActivePasswordMetricsAvailable(storedHash.type, credential, userId);
+            setUserPasswordMetrics(storedHash.type, credential, userId);
             unlockKeystore(credential, userId);
 
             Slog.i(TAG, "Unlocking user " + userId + " with token length "
@@ -1988,32 +2007,40 @@
     }
 
     /**
-     * Call this method to notify DPMS regarding the latest password metric. This should be called
-     * when the user is authenticating or when a new password is being set.
+     * Keep track of the given user's latest password metric. This should be called
+     * when the user is authenticating or when a new password is being set. In comparison,
+     * {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
      */
-    private void notifyActivePasswordMetricsAvailable(
-            @CredentialType int credentialType, byte[] password, @UserIdInt int userId) {
-        final PasswordMetrics metrics =
-                PasswordMetrics.computeForCredential(credentialType, password);
+    private void setUserPasswordMetrics(@CredentialType int credentialType, byte[] password,
+            @UserIdInt int userHandle) {
+        synchronized (this) {
+            mUserPasswordMetrics.put(userHandle,
+                    PasswordMetrics.computeForCredential(credentialType, password));
+        }
+    }
 
-        // Asynchronous to avoid dead lock
-        mHandler.post(() -> {
-            final DevicePolicyManager dpm = (DevicePolicyManager)
-                    mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-            dpm.setActivePasswordState(metrics, userId);
-        });
+    @VisibleForTesting
+    PasswordMetrics getUserPasswordMetrics(int userHandle) {
+        if (!isUserSecure(userHandle)) {
+            // for users without password, mUserPasswordMetrics might not be initialized
+            // since the user never unlock the device manually. In this case, always
+            // return a default metrics object. This is to distinguish this case from
+            // the case where during boot user password is unknown yet (returning null here)
+            return new PasswordMetrics();
+        }
+        synchronized (this) {
+            return mUserPasswordMetrics.get(userHandle);
+        }
     }
 
     /**
-     * Call after {@link #notifyActivePasswordMetricsAvailable} so metrics are updated before
+     * Call after {@link #setUserPasswordMetrics} so metrics are updated before
      * reporting the password changed.
      */
     private void notifyPasswordChanged(@UserIdInt int userId) {
         // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
         mHandler.post(() -> {
-            DevicePolicyManager dpm = (DevicePolicyManager)
-                    mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-            dpm.reportPasswordChanged(userId);
+            mInjector.getDevicePolicyManager().reportPasswordChanged(userId);
             LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
         });
     }
@@ -2582,7 +2609,7 @@
         }
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            notifyActivePasswordMetricsAvailable(credentialType, userCredential, userId);
+            setUserPasswordMetrics(credentialType, userCredential, userId);
             unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
 
             // Do resetLockout / revokeChallenge when all profiles are unlocked
@@ -2676,7 +2703,7 @@
         setSyntheticPasswordHandleLocked(newHandle, userId);
         synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
 
-        notifyActivePasswordMetricsAvailable(credentialType, credential, userId);
+        setUserPasswordMetrics(credentialType, credential, userId);
 
         if (profilePasswords != null) {
             for (Map.Entry<Integer, byte[]> entry : profilePasswords.entrySet()) {
@@ -2688,7 +2715,7 @@
     }
 
     @GuardedBy("mSpManager")
-    private void spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
+    private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
             byte[] savedCredential, int requestedQuality, int userId,
             boolean allowUntrustedChange, boolean isLockTiedToParent) {
         if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
@@ -2713,8 +2740,9 @@
 
         // If existing credential is provided, the existing credential must match.
         if (savedCredential != null && auth == null) {
-            throw new IllegalStateException(String.format("Failed to enroll %s",
+            Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
                     credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+            return false;
         }
         boolean untrustedReset = false;
         if (auth != null) {
@@ -2734,7 +2762,8 @@
             }
             untrustedReset = true;
         } else /* responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
-            throw new IllegalStateException("Rate limit exceeded, so password was not changed.");
+            Slog.w(TAG, "Rate limit exceeded, so password was not changed.");
+            return false;
         }
 
         if (auth != null) {
@@ -2756,6 +2785,7 @@
             // synthetic password. That would invalidate existing escrow tokens though.
         }
         sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent);
+        return true;
     }
 
     /**
@@ -2982,6 +3012,8 @@
             pw.println("hasPassword: " + havePassword(userId));
             pw.println("hasPattern: " + havePattern(userId)); // print raw credential type instead?
             pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabled(userId));
+            pw.println(String.format("metrics: %s",
+                    getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
             pw.decreaseIndent();
         }
         pw.println();
@@ -3158,5 +3190,23 @@
         public boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId) {
             return LockSettingsService.this.unlockUserWithToken(tokenHandle, token, userId);
         }
+
+        @Override
+        public PasswordMetrics getUserPasswordMetrics(int userHandle) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                if (isManagedProfileWithUnifiedLock(userHandle)) {
+                    // A managed profile with unified challenge is supposed to be protected by the
+                    // parent lockscreen, so asking for its password metrics is not really useful,
+                    // as this method would just return the metrics of the random profile password
+                    Slog.w(TAG, "Querying password metrics for unified challenge profile: "
+                            + userHandle);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return LockSettingsService.this.getUserPasswordMetrics(userHandle);
+        }
+
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index fe12a94..5594614 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -47,6 +47,7 @@
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
@@ -325,23 +326,16 @@
             version = mCache.getVersion();
         }
 
-        RandomAccessFile raf = null;
         byte[] stored = null;
-        try {
-            raf = new RandomAccessFile(name, "r");
+        try (RandomAccessFile raf = new RandomAccessFile(name, "r")) {
             stored = new byte[(int) raf.length()];
             raf.readFully(stored, 0, stored.length);
             raf.close();
+        } catch (FileNotFoundException suppressed) {
+            // readFile() is also called by hasFile() to check the existence of files, in this
+            // case FileNotFoundException is expected.
         } catch (IOException e) {
             Slog.e(TAG, "Cannot read file " + e);
-        } finally {
-            if (raf != null) {
-                try {
-                    raf.close();
-                } catch (IOException e) {
-                    Slog.e(TAG, "Error closing file " + e);
-                }
-            }
         }
         mCache.putFileIfUnchanged(name, stored, version);
         return stored;
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index 3f15b38..77fbe41 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.provider.Settings.ACTION_VPN_SETTINGS;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -32,7 +34,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
-import android.os.INetworkManagementService;
+import android.os.Handler;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.text.TextUtils;
@@ -63,22 +65,19 @@
 
     private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
 
-    private static final int ROOT_UID = 0;
+    @NonNull private final Context mContext;
+    @NonNull private final ConnectivityService mConnService;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Vpn mVpn;
+    @NonNull private final VpnProfile mProfile;
 
-    private final Context mContext;
-    private final INetworkManagementService mNetService;
-    private final ConnectivityService mConnService;
-    private final Vpn mVpn;
-    private final VpnProfile mProfile;
+    @NonNull private final Object mStateLock = new Object();
 
-    private final Object mStateLock = new Object();
+    @NonNull private final PendingIntent mConfigIntent;
+    @NonNull private final PendingIntent mResetIntent;
 
-    private final PendingIntent mConfigIntent;
-    private final PendingIntent mResetIntent;
-
+    @Nullable
     private String mAcceptedEgressIface;
-    private String mAcceptedIface;
-    private List<LinkAddress> mAcceptedSourceAddr;
 
     private int mErrorCount;
 
@@ -86,11 +85,14 @@
         return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
     }
 
-    public LockdownVpnTracker(Context context, INetworkManagementService netService,
-            ConnectivityService connService, Vpn vpn, VpnProfile profile) {
+    public LockdownVpnTracker(@NonNull Context context,
+            @NonNull ConnectivityService connService,
+            @NonNull Handler handler,
+            @NonNull Vpn vpn,
+            @NonNull VpnProfile profile) {
         mContext = Preconditions.checkNotNull(context);
-        mNetService = Preconditions.checkNotNull(netService);
         mConnService = Preconditions.checkNotNull(connService);
+        mHandler = Preconditions.checkNotNull(handler);
         mVpn = Preconditions.checkNotNull(vpn);
         mProfile = Preconditions.checkNotNull(profile);
 
@@ -176,11 +178,6 @@
             final String iface = vpnConfig.interfaze;
             final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
 
-            if (TextUtils.equals(iface, mAcceptedIface)
-                  && sourceAddrs.equals(mAcceptedSourceAddr)) {
-                return;
-            }
-
             Slog.d(TAG, "VPN connected using iface=" + iface +
                     ", sourceAddr=" + sourceAddrs.toString());
             EventLogTags.writeLockdownVpnConnected(egressType);
@@ -205,7 +202,7 @@
         mVpn.setLockdown(true);
 
         final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
-        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
+        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, mHandler);
 
         handleStateChangedLocked();
     }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index b4a8099..976a0c6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -217,6 +217,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -3281,7 +3282,7 @@
 
     @Override
     public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
-            long timeoutMillis, String callingPackage) {
+            long networkTypeMask, long timeoutMillis, String callingPackage) {
         enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
 
         // We can only override when carrier told us about plans
@@ -3299,11 +3300,16 @@
         final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                 NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
         if (overrideEnabled || overrideValue == 0) {
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
-                    overrideMask, overrideValue, subId));
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = subId;
+            args.arg2 = overrideMask;
+            args.arg3 = overrideValue;
+            args.arg4 = networkTypeMask;
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args));
             if (timeoutMillis > 0) {
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
-                        overrideMask, 0, subId), timeoutMillis);
+                args.arg3 = 0;
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args),
+                        timeoutMillis);
             }
         }
     }
@@ -4439,10 +4445,11 @@
     }
 
     private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
-            int overrideMask, int overrideValue) {
+            int overrideMask, int overrideValue, long networkTypeMask) {
         if (listener != null) {
             try {
-                listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+                listener.onSubscriptionOverride(subId, overrideMask, overrideValue,
+                        networkTypeMask);
             } catch (RemoteException ignored) {
             }
         }
@@ -4543,13 +4550,16 @@
                     return true;
                 }
                 case MSG_SUBSCRIPTION_OVERRIDE: {
-                    final int overrideMask = msg.arg1;
-                    final int overrideValue = msg.arg2;
-                    final int subId = (int) msg.obj;
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final int subId = (int) args.arg1;
+                    final int overrideMask = (int) args.arg2;
+                    final int overrideValue = (int) args.arg3;
+                    final long networkTypeMask = (long) args.arg4;
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
-                        dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
+                        dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue,
+                                networkTypeMask);
                     }
                     mListeners.finishBroadcast();
                     return true;
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index 358bdb9..e59bf16 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -41,10 +41,9 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
-        boolean userWantsBubbles = mConfig.bubblesEnabled(record.sbn.getUser());
         boolean appCanShowBubble =
                 mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid());
-        if (!userWantsBubbles || !appCanShowBubble) {
+        if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
             record.setAllowBubble(false);
         } else {
             if (record.getChannel() != null) {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 64477a5..aed2927 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -38,6 +38,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,6 +84,39 @@
     }
 
     @Override
+    protected ArrayMap<Boolean, ArrayList<ComponentName>>
+            resetComponents(String packageName, int userId) {
+        resetPackage(packageName, userId);
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, new ArrayList<>(0));
+        changes.put(false, new ArrayList<>(0));
+        return changes;
+    }
+
+    /**
+     *  @return true if the passed package is enabled. false otherwise
+     */
+    boolean resetPackage(String packageName, int userId) {
+        boolean isAllowed = super.isPackageOrComponentAllowed(packageName, userId);
+        boolean isDefault = super.isDefaultComponentOrPackage(packageName);
+        if (!isAllowed && isDefault) {
+            setPackageOrComponentEnabled(packageName, userId, true, true);
+        }
+        if (isAllowed && !isDefault) {
+            setPackageOrComponentEnabled(packageName, userId, true, false);
+        }
+        return !isAllowed && isDefault;
+    }
+
+    @Override
+    void writeDefaults(XmlSerializer out) throws IOException {
+        synchronized (mDefaultsLock) {
+            String defaults = String.join(ENABLED_SERVICES_SEPARATOR, mDefaultPackages);
+            out.attribute(null, ATT_DEFAULTS, defaults);
+        }
+    }
+
+    @Override
     protected Config getConfig() {
         final Config c = new Config();
         c.caption = "condition provider";
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 4828bbf..48b0fd6 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -105,6 +105,7 @@
     static final String ATT_USER_ID = "user";
     static final String ATT_IS_PRIMARY = "primary";
     static final String ATT_VERSION = "version";
+    static final String ATT_DEFAULTS = "defaults";
 
     static final int DB_VERSION = 1;
 
@@ -128,6 +129,10 @@
      */
     private final ArrayList<Pair<ComponentName, Integer>> mServicesBound = new ArrayList<>();
     private final ArraySet<Pair<ComponentName, Integer>> mServicesRebinding = new ArraySet<>();
+    // we need these packages to be protected because classes that inherit from it need to see it
+    protected final Object mDefaultsLock = new Object();
+    protected final ArraySet<ComponentName> mDefaultComponents = new ArraySet<>();
+    protected final ArraySet<String> mDefaultPackages = new ArraySet<>();
 
     // lists the component names of all enabled (and therefore potentially connected)
     // app services for current profiles.
@@ -179,6 +184,112 @@
         }
     }
 
+    protected void addDefaultComponentOrPackage(String packageOrComponent) {
+        if (packageOrComponent != null) {
+            synchronized (mDefaultsLock) {
+                ComponentName cn = ComponentName.unflattenFromString(packageOrComponent);
+                if (cn == null) {
+                    mDefaultPackages.add(packageOrComponent);
+                } else {
+                    mDefaultPackages.add(cn.getPackageName());
+                    mDefaultComponents.add(cn);
+                }
+            }
+        }
+    }
+
+    boolean isDefaultComponentOrPackage(String packageOrComponent) {
+        synchronized (mDefaultsLock) {
+            ComponentName cn = ComponentName.unflattenFromString(packageOrComponent);
+            if (cn == null) {
+                return mDefaultPackages.contains(packageOrComponent);
+            } else {
+                return mDefaultComponents.contains(cn);
+            }
+        }
+    }
+
+    ArraySet<ComponentName> getDefaultComponents() {
+        synchronized (mDefaultsLock) {
+            return new ArraySet<>(mDefaultComponents);
+        }
+    }
+
+    ArraySet<String> getDefaultPackages() {
+        synchronized (mDefaultsLock) {
+            return new ArraySet<>(mDefaultPackages);
+        }
+    }
+
+    /**
+     * When resetting a package, we need to enable default components that belong to that packages
+     * we also need to disable components that are not default to return the managed service state
+     * to when a new android device is first turned on for that package.
+     *
+     * @param packageName package to reset.
+     * @param userId the android user id
+     * @return a list of components that were permitted
+     */
+    @NonNull
+    ArrayMap<Boolean, ArrayList<ComponentName>> resetComponents(String packageName, int userId) {
+        // components that we want to enable
+        ArrayList<ComponentName> componentsToEnable =
+                new ArrayList<>(mDefaultComponents.size());
+
+        // components that were removed
+        ArrayList<ComponentName> disabledComponents =
+                new ArrayList<>(mDefaultComponents.size());
+
+        // all components that are enabled now
+        ArraySet<ComponentName> enabledComponents =
+                new ArraySet<>(getAllowedComponents(userId));
+
+        boolean changed = false;
+
+        synchronized (mDefaultsLock) {
+            // record all components that are enabled but should not be by default
+            for (int i = 0; i < mDefaultComponents.size() && enabledComponents.size() > 0; i++) {
+                ComponentName currentDefault = mDefaultComponents.valueAt(i);
+                if (packageName.equals(currentDefault.getPackageName())
+                        && !enabledComponents.contains(currentDefault)) {
+                    componentsToEnable.add(currentDefault);
+                }
+            }
+            synchronized (mApproved) {
+                final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(
+                        userId);
+                if (approvedByType != null) {
+                    final int M = approvedByType.size();
+                    for (int j = 0; j < M; j++) {
+                        final ArraySet<String> approved = approvedByType.valueAt(j);
+                        for (int i = 0; i < enabledComponents.size(); i++) {
+                            ComponentName currentComponent = enabledComponents.valueAt(i);
+                            if (packageName.equals(currentComponent.getPackageName())
+                                    && !mDefaultComponents.contains(currentComponent)) {
+                                if (approved.remove(currentComponent.flattenToString())) {
+                                    disabledComponents.add(currentComponent);
+                                    changed = true;
+                                }
+                            }
+                        }
+                        for (int i = 0; i < componentsToEnable.size(); i++) {
+                            ComponentName candidate = componentsToEnable.get(i);
+                            changed |= approved.add(candidate.flattenToString());
+                        }
+                    }
+
+                }
+            }
+        }
+        if (changed) rebindServices(false, USER_ALL);
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, componentsToEnable);
+        changes.put(false, disabledComponents);
+
+        return changes;
+    }
+
     protected int getBindFlags() {
         return BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT;
     }
@@ -310,11 +421,24 @@
         }
     }
 
+    void writeDefaults(XmlSerializer out) throws IOException {
+        synchronized (mDefaultsLock) {
+            List<String> componentStrings = new ArrayList<>(mDefaultComponents.size());
+            for (int i = 0; i < mDefaultComponents.size(); i++) {
+                componentStrings.add(mDefaultComponents.valueAt(i).flattenToString());
+            }
+            String defaults = String.join(ENABLED_SERVICES_SEPARATOR, componentStrings);
+            out.attribute(null, ATT_DEFAULTS, defaults);
+        }
+    }
+
     public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
         out.startTag(null, getConfig().xmlTag);
 
         out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
 
+        writeDefaults(out);
+
         if (forBackup) {
             trimApprovedListsAccordingToInstalledServices(userId);
         }
@@ -378,6 +502,27 @@
         loadAllowedComponentsFromSettings();
     }
 
+    void readDefaults(XmlPullParser parser) {
+        String defaultComponents = XmlUtils.readStringAttribute(parser, ATT_DEFAULTS);
+        if (defaultComponents == null) {
+            return;
+        }
+        String[] components = defaultComponents.split(ENABLED_SERVICES_SEPARATOR);
+        synchronized (mDefaultsLock) {
+            for (int i = 0; i < components.length; i++) {
+                if (!TextUtils.isEmpty(components[i])) {
+                    ComponentName cn = ComponentName.unflattenFromString(components[i]);
+                    if (cn != null) {
+                        mDefaultPackages.add(cn.getPackageName());
+                        mDefaultComponents.add(cn);
+                    } else {
+                        mDefaultPackages.add(components[i]);
+                    }
+                }
+            }
+        }
+    }
+
     public void readXml(
             XmlPullParser parser,
             TriPredicate<String, Integer, String> allowedManagedServicePackages,
@@ -386,6 +531,7 @@
             throws XmlPullParserException, IOException {
         // read grants
         int type;
+        readDefaults(parser);
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             String tag = parser.getName();
             if (type == XmlPullParser.END_TAG
@@ -785,9 +931,9 @@
         return queryPackageForServices(packageName, 0, userId);
     }
 
-    protected Set<ComponentName> queryPackageForServices(String packageName, int extraFlags,
+    protected ArraySet<ComponentName> queryPackageForServices(String packageName, int extraFlags,
             int userId) {
-        Set<ComponentName> installed = new ArraySet<>();
+        ArraySet<ComponentName> installed = new ArraySet<>();
         final PackageManager pm = mContext.getPackageManager();
         Intent queryIntent = new Intent(mConfig.serviceInterface);
         if (!TextUtils.isEmpty(packageName)) {
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 9b9f4de..bc05154 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -129,6 +129,12 @@
             return -1 * Integer.compare(leftPriority, rightPriority);
         }
 
+        final boolean leftInterruptive = left.isInterruptive();
+        final boolean rightInterruptive = right.isInterruptive();
+        if (leftInterruptive != rightInterruptive) {
+            return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+        }
+
         // then break ties by time, most recent first
         return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 30b2245..d480cb6e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -94,6 +94,7 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.PreferencesHelper.DEFAULT_ALLOW_BUBBLE;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -355,7 +356,6 @@
     private static final int REQUEST_CODE_TIMEOUT = 1;
     private static final String SCHEME_TIMEOUT = "timeout";
     private static final String EXTRA_KEY = "key";
-
     private IActivityManager mAm;
     private ActivityManager mActivityManager;
     private IPackageManager mPackageManager;
@@ -456,7 +456,7 @@
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
     private static final IBinder WHITELIST_TOKEN = new Binder();
-    private RankingHandler mRankingHandler;
+    protected RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 
@@ -523,24 +523,21 @@
 
     }
 
-    protected void readDefaultApprovedServices(int userId) {
+
+    void loadDefaultApprovedServices(int userId) {
         String defaultListenerAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultListenerAccessPackages);
         if (defaultListenerAccess != null) {
-            for (String whitelisted :
-                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
-                // Gather all notification listener components for candidate pkgs.
-                Set<ComponentName> approvedListeners =
-                        mListeners.queryPackageForServices(whitelisted,
+            String[] listeners =
+                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < listeners.length; i++) {
+                ArraySet<ComponentName> approvedListeners =
+                        mListeners.queryPackageForServices(listeners[i],
                                 MATCH_DIRECT_BOOT_AWARE
                                         | MATCH_DIRECT_BOOT_UNAWARE, userId);
-                for (ComponentName cn : approvedListeners) {
-                    try {
-                        getBinderService().setNotificationListenerAccessGrantedForUser(cn,
-                                    userId, true);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
+                for (int k = 0; k < approvedListeners.size(); k++) {
+                    ComponentName cn = approvedListeners.valueAt(k);
+                    mListeners.addDefaultComponentOrPackage(cn.flattenToString());
                 }
             }
         }
@@ -548,46 +545,86 @@
         String defaultDndAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultDndAccessPackages);
         if (defaultDndAccess != null) {
-            for (String whitelisted :
-                    defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
-                try {
-                    getBinderService().setNotificationPolicyAccessGranted(whitelisted, true);
-                } catch (RemoteException e) {
-                    e.printStackTrace();
-                }
+            String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < dnds.length; i++) {
+                mConditionProviders.addDefaultComponentOrPackage(dnds[i]);
             }
         }
 
+
+        ArraySet<String> assistants = new ArraySet<>();
+        String deviceAssistant = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE);
+        if (deviceAssistant != null) {
+            assistants.addAll(Arrays.asList(deviceAssistant.split(
+                    ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+        }
+        assistants.addAll(Arrays.asList(getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultAssistantAccessComponent)
+                .split(ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+        for (int i = 0; i < assistants.size(); i++) {
+            String cnString = assistants.valueAt(i);
+            mAssistants.addDefaultComponentOrPackage(cnString);
+        }
+    }
+
+    protected void allowDefaultApprovedServices(int userId) {
+
+        ArraySet<ComponentName> defaultListeners = mListeners.getDefaultComponents();
+        for (int i = 0; i < defaultListeners.size(); i++) {
+            ComponentName cn = defaultListeners.valueAt(i);
+            allowNotificationListener(userId, cn);
+        }
+
+        ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
+        for (int i = 0; i < defaultDnds.size(); i++) {
+            allowDndPackage(defaultDnds.valueAt(i));
+        }
+
         setDefaultAssistantForUser(userId);
     }
 
     protected void setDefaultAssistantForUser(int userId) {
-        List<ComponentName> validAssistants = new ArrayList<>(
-                mAssistants.queryPackageForServices(
-                        null, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId));
-
-        List<String> candidateStrs = new ArrayList<>();
-        candidateStrs.add(DeviceConfig.getProperty(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE));
-        candidateStrs.add(getContext().getResources().getString(
-                com.android.internal.R.string.config_defaultAssistantAccessComponent));
-
-        for (String candidateStr : candidateStrs) {
-            if (TextUtils.isEmpty(candidateStr)) {
-                continue;
-            }
-            ComponentName candidate = ComponentName.unflattenFromString(candidateStr);
-            if (candidate != null && validAssistants.contains(candidate)) {
-                setNotificationAssistantAccessGrantedForUserInternal(candidate, userId, true);
-                Slog.d(TAG, String.format("Set default NAS to be %s in %d", candidateStr, userId));
-                return;
-            } else {
-                Slog.w(TAG, "Invalid default NAS config is found: " + candidateStr);
-            }
+        ArraySet<ComponentName> defaults = mAssistants.getDefaultComponents();
+        // We should have only one default assistant by default
+        // allowAssistant should execute once in practice
+        for (int i = 0; i < defaults.size(); i++) {
+            ComponentName cn = defaults.valueAt(i);
+            if (allowAssistant(userId, cn)) return;
         }
     }
 
+    private void allowDndPackage(String packageName) {
+        try {
+            getBinderService().setNotificationPolicyAccessGranted(packageName, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void allowNotificationListener(int userId, ComponentName cn) {
+
+        try {
+            getBinderService().setNotificationListenerAccessGrantedForUser(cn,
+                        userId, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean allowAssistant(int userId, ComponentName candidate) {
+        Set<ComponentName> validAssistants =
+                mAssistants.queryPackageForServices(
+                        null,
+                        MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+        if (candidate != null && validAssistants.contains(candidate)) {
+            setNotificationAssistantAccessGrantedForUserInternal(candidate, userId, true);
+            return true;
+        }
+        return false;
+    }
+
     void readPolicyXml(InputStream stream, boolean forRestore, int userId)
             throws XmlPullParserException, NumberFormatException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
@@ -655,7 +692,8 @@
             } catch (FileNotFoundException e) {
                 // No data yet
                 // Load default managed services approvals
-                readDefaultApprovedServices(USER_SYSTEM);
+                loadDefaultApprovedServices(USER_SYSTEM);
+                allowDefaultApprovedServices(USER_SYSTEM);
             } catch (IOException e) {
                 Log.wtf(TAG, "Unable to read notification policy", e);
             } catch (NumberFormatException e) {
@@ -925,7 +963,7 @@
                         () -> mAm.crashApplication(uid, initialPid, pkg, -1,
                             "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
                                 + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
-                                + message));
+                                + message, true /* force */));
             }
         }
 
@@ -1362,7 +1400,7 @@
                 if (userId != USER_NULL) {
                     mUserProfiles.updateCache(context);
                     if (!mUserProfiles.isManagedProfile(userId)) {
-                        readDefaultApprovedServices(userId);
+                        allowDefaultApprovedServices(userId);
                     }
                 }
             } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
@@ -1391,7 +1429,9 @@
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
-        private final Uri NOTIFICATION_BUBBLES_URI
+        private final Uri NOTIFICATION_BUBBLES_URI_GLOBAL
+                = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_BUBBLES);
+        private final Uri NOTIFICATION_BUBBLES_URI_SECURE
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
         private final Uri NOTIFICATION_LIGHT_PULSE_URI
                 = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
@@ -1410,7 +1450,9 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
                     false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_GLOBAL,
+                    false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_SECURE,
                     false, this, UserHandle.USER_ALL);
             update(null);
         }
@@ -1437,9 +1479,41 @@
             if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
                 mPreferencesHelper.updateBadgingEnabled();
             }
-            if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) {
+            // In QPR we moved the setting to Global rather than Secure so that the setting
+            // applied to work profiles. Unfortunately we need to maintain both to pass CTS without
+            // a change to CTS outside of a normal letter release.
+            if (uri == null || NOTIFICATION_BUBBLES_URI_GLOBAL.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_GLOBAL);
                 mPreferencesHelper.updateBubblesEnabled();
             }
+            if (NOTIFICATION_BUBBLES_URI_SECURE.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_SECURE);
+            }
+        }
+
+        private void syncBubbleSettings(ContentResolver resolver, Uri settingToFollow) {
+            boolean followSecureSetting = settingToFollow.equals(NOTIFICATION_BUBBLES_URI_SECURE);
+
+            int secureSettingValue = Settings.Secure.getInt(resolver,
+                    Settings.Secure.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+            int globalSettingValue = Settings.Global.getInt(resolver,
+                    Settings.Global.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+
+            if (globalSettingValue == secureSettingValue) {
+                return;
+            }
+
+            if (followSecureSetting) {
+                // Global => secure
+                Settings.Global.putInt(resolver,
+                        Settings.Global.NOTIFICATION_BUBBLES,
+                        secureSettingValue);
+            } else {
+                // Secure => Global
+                Settings.Secure.putInt(resolver,
+                        Settings.Secure.NOTIFICATION_BADGING,
+                        globalSettingValue);
+            }
         }
     }
 
@@ -1513,10 +1587,12 @@
 
     @VisibleForTesting
     void clearNotifications() {
-        mEnqueuedNotifications.clear();
-        mNotificationList.clear();
-        mNotificationsByKey.clear();
-        mSummaryByGroupKey.clear();
+        synchronized (mNotificationList) {
+            mEnqueuedNotifications.clear();
+            mNotificationList.clear();
+            mNotificationsByKey.clear();
+            mSummaryByGroupKey.clear();
+        }
     }
 
     @VisibleForTesting
@@ -1568,11 +1644,6 @@
     void setPreferencesHelper(PreferencesHelper prefHelper) { mPreferencesHelper = prefHelper; }
 
     @VisibleForTesting
-    void setRankingHandler(RankingHandler rankingHandler) {
-        mRankingHandler = rankingHandler;
-    }
-
-    @VisibleForTesting
     void setZenHelper(ZenModeHelper zenHelper) {
         mZenModeHelper = zenHelper;
     }
@@ -1604,7 +1675,7 @@
 
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
-    void init(Looper looper, IPackageManager packageManager,
+    void init(Looper looper, RankingHandler rankingHandler, IPackageManager packageManager,
             PackageManager packageManagerClient,
             LightsManager lightsManager, NotificationListeners notificationListeners,
             NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
@@ -1638,7 +1709,6 @@
         mUm = userManager;
 
         mHandler = new WorkerHandler(looper);
-        mRankingThread.start();
         String[] extractorNames;
         try {
             extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -1647,7 +1717,7 @@
         }
         mUsageStats = usageStats;
         mMetricsLogger = new MetricsLogger();
-        mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
+        mRankingHandler = rankingHandler;
         mConditionProviders = conditionProviders;
         mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@@ -1708,7 +1778,6 @@
 
         mPolicyFile = policyFile;
         loadPolicyFile();
-
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         if (mStatusBar != null) {
             mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -1792,8 +1861,9 @@
         }, mUserProfiles);
 
         final File systemDir = new File(Environment.getDataDirectory(), "system");
+        mRankingThread.start();
 
-        init(Looper.myLooper(),
+        init(Looper.myLooper(), new RankingHandlerWorker(mRankingThread.getLooper()),
                 AppGlobals.getPackageManager(), getContext().getPackageManager(),
                 getLocalService(LightsManager.class),
                 new NotificationListeners(AppGlobals.getPackageManager()),
@@ -2923,21 +2993,52 @@
 
         @Override
         public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
+            boolean packagesChanged = false;
             checkCallerIsSystem();
-
             // Cancel posted notifications
+            final int userId = UserHandle.getUserId(uid);
             cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
                     UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
 
-            final String[] packages = new String[] {packageName};
-            final int[] uids = new int[] {uid};
-
-            // Listener & assistant
-            mListeners.onPackagesChanged(true, packages, uids);
-            mAssistants.onPackagesChanged(true, packages, uids);
-
             // Zen
-            mConditionProviders.onPackagesChanged(true, packages, uids);
+            packagesChanged |=
+                    mConditionProviders.resetPackage(packageName, userId);
+
+            // Listener
+            ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
+                    mListeners.resetComponents(packageName, userId);
+            packagesChanged |= changedListeners.get(true).size() > 0
+                    || changedListeners.get(false).size() > 0;
+
+            // When a listener is enabled, we enable the dnd package as a secondary
+            for (int i = 0; i < changedListeners.get(true).size(); i++) {
+                mConditionProviders.setPackageOrComponentEnabled(
+                        changedListeners.get(true).get(i).getPackageName(),
+                        userId, false, true);
+            }
+
+            // Assistant
+            ArrayMap<Boolean, ArrayList<ComponentName>> changedAssistants =
+                    mAssistants.resetComponents(packageName, userId);
+            packagesChanged |= changedAssistants.get(true).size() > 0
+                    || changedAssistants.get(false).size() > 0;
+
+            // we want only one assistant enabled
+            for (int i = 1; i < changedAssistants.get(true).size(); i++) {
+                mAssistants.setPackageOrComponentEnabled(
+                        changedAssistants.get(true).get(i).flattenToString(),
+                        userId, true, false);
+            }
+
+            // When the default assistant is enabled, we enable the dnd package as a secondary
+            if (changedAssistants.get(true).size() > 0) {
+                //we want only one assistant active
+                mConditionProviders
+                        .setPackageOrComponentEnabled(
+                                changedAssistants.get(true).get(0).getPackageName(),
+                                userId, false, true);
+
+            }
 
             // Snoozing
             mSnoozeHelper.clearData(UserHandle.getUserId(uid), packageName);
@@ -2947,6 +3048,14 @@
                 mPreferencesHelper.clearData(packageName, uid);
             }
 
+            if (packagesChanged) {
+                getContext().sendBroadcastAsUser(new Intent(
+                                ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                                .setPackage(packageName)
+                                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
+                        UserHandle.of(userId), null);
+            }
+
             handleSavePolicyFile();
         }
 
@@ -4958,47 +5067,112 @@
         } else {
             notification.flags &= ~FLAG_BUBBLE;
         }
+        // Is the app in the foreground?
+        final boolean appIsForeground =
+                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (!appIsForeground && metadata != null) {
+            // Remove any flags that only work when foregrounded
+            int flags = metadata.getFlags();
+            flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+            flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+            metadata.setFlags(flags);
+        }
     }
 
     /**
-     * @return whether the provided notification record is allowed to be represented as a bubble.
+     * @return whether the provided notification record is allowed to be represented as a bubble,
+     * accounting for user choice & policy.
      */
     private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
-        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
-        boolean intentCanBubble = metadata != null
-                && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg);
+        if (!canBubble(r, pkg, userId)) {
+            // no log: canBubble has its own
+            return false;
+        }
 
-        // Does the app want to bubble & is able to bubble
-        boolean canBubble = intentCanBubble
-                && mPreferencesHelper.areBubblesAllowed(pkg, userId)
-                && mPreferencesHelper.bubblesEnabled(r.sbn.getUser())
-                && r.getChannel().canBubble()
-                && !mActivityManager.isLowRamDevice();
+        if (mActivityManager.isLowRamDevice()) {
+            logBubbleError(r.getKey(), "low ram device");
+            return false;
+        }
 
-        // Is the app in the foreground?
-        final boolean appIsForeground =
-                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        if (mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND) {
+            // If the app is foreground it always gets to bubble
+            return true;
+        }
 
-        // Is the notification something we'd allow to bubble?
-        // A call with a foreground service + person
+        if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
+            // This is an update to an active bubble
+            return true;
+        }
+
+        // At this point the bubble must fulfill communication policy
+
+        // Communication always needs a person
         ArrayList<Person> peopleList = notification.extras != null
                 ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
                 : null;
-        boolean isForegroundCall = CATEGORY_CALL.equals(notification.category)
-                && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
-        // OR message style (which always has a person) with any remote input
-        Class<? extends Notification.Style> style = notification.getNotificationStyle();
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        boolean notificationAppropriateToBubble =
-                (isMessageStyle && hasValidRemoteInput(notification))
-                || (peopleList != null && !peopleList.isEmpty() && isForegroundCall);
+        // Message style requires a person & it's not included in the list
+        boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+                notification.getNotificationStyle());
+        if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+            logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+                    + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+            return false;
+        }
 
-        // OR something that was previously a bubble & still exists
-        boolean bubbleUpdate = oldRecord != null
-                && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
-        return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate);
+        // Communication is a message or a call
+        boolean isCall = CATEGORY_CALL.equals(notification.category);
+        boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+        if (isMessageStyle) {
+            if (hasValidRemoteInput(notification)) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "messages require valid remote input");
+            return false;
+        } else if (isCall) {
+            if (hasForegroundService) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "calls require foreground service");
+            return false;
+        }
+        logBubbleError(r.getKey(), "if not foreground, must be "
+                + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+        return false;
+    }
+
+    /**
+     * @return whether the user has enabled the provided notification to bubble, does not account
+     * for policy.
+     */
+    private boolean canBubble(NotificationRecord r, String pkg, int userId) {
+        Notification notification = r.getNotification();
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (metadata == null) {
+            // no log: no need to inform dev if they didn't attach bubble metadata
+            return false;
+        }
+        if (!canLaunchInActivityView(getContext(), metadata.getIntent(), pkg)) {
+            // no log: method has the failure log
+            return false;
+        }
+        if (!mPreferencesHelper.bubblesEnabled()) {
+            logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+            return false;
+        }
+        if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
+            logBubbleError(r.getKey(),
+                    "bubbles for package: " + pkg + " disabled for user: " + userId);
+            return false;
+        }
+        if (!r.getChannel().canBubble()) {
+            logBubbleError(r.getKey(),
+                    "bubbles for channel " + r.getChannel().getId() + " disabled");
+            return false;
+        }
+        return true;
     }
 
     private boolean hasValidRemoteInput(Notification n) {
@@ -5017,6 +5191,11 @@
         return false;
     }
 
+    private void logBubbleError(String key, String failureMessage) {
+        if (DBG) {
+            Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+        }
+    }
     /**
      * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
      *
@@ -5365,8 +5544,16 @@
             }
 
             synchronized (mNotificationLock) {
-                // Look for the notification, searching both the posted and enqueued lists.
-                NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
+                // If the notification is currently enqueued, repost this runnable so it has a
+                // chance to notify listeners
+                if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
+                        != null) {
+                    mHandler.post(this);
+                    return;
+                }
+                // Look for the notification in the posted list, since we already checked enqueued.
+                NotificationRecord r =
+                        findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
                 if (r != null) {
                     // The notification was found, check if it should be removed.
 
@@ -5383,12 +5570,26 @@
                         return;
                     }
 
+                    // Bubbled children get to stick around if the summary was manually cancelled
+                    // (user removed) from systemui.
+                    FlagChecker childrenFlagChecker = null;
+                    if (mReason == REASON_CANCEL
+                            || mReason == REASON_CLICK
+                            || mReason == REASON_CANCEL_ALL) {
+                        childrenFlagChecker = (flags) -> {
+                            if ((flags & FLAG_BUBBLE) != 0) {
+                                return false;
+                            }
+                            return true;
+                        };
+                    }
+
                     // Cancel the notification.
                     boolean wasPosted = removeFromNotificationListsLocked(r);
                     cancelNotificationLocked(
                             r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, null);
+                            mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
@@ -5558,7 +5759,9 @@
                         notification.flags |=
                                 old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                         r.isUpdate = true;
-                        r.setTextChanged(isVisuallyInterruptive(old, r));
+                        final boolean isInterruptive = isVisuallyInterruptive(old, r);
+                        r.setTextChanged(isInterruptive);
+                        r.setInterruptive(isInterruptive);
                     }
 
                     mNotificationsByKey.put(n.getKey(), r);
@@ -5657,7 +5860,6 @@
 
         Notification oldN = old.sbn.getNotification();
         Notification newN = r.sbn.getNotification();
-
         if (oldN.extras == null || newN.extras == null) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5689,6 +5891,7 @@
             }
             return true;
         }
+
         // Do not compare Spannables (will always return false); compare unstyled Strings
         final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
         final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
@@ -5703,6 +5906,7 @@
             }
             return true;
         }
+
         if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5710,6 +5914,16 @@
             }
             return true;
         }
+
+        // Fields below are invisible to bubbles.
+        if (r.canBubble()) {
+            if (DEBUG_INTERRUPTIVENESS) {
+                Slog.v(TAG, "INTERRUPTIVENESS: "
+                        +  r.getKey() + " is not interruptive: bubble");
+            }
+            return false;
+        }
+
         // Actions
         if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
             if (DEBUG_INTERRUPTIVENESS) {
@@ -5743,7 +5957,6 @@
         } catch (Exception e) {
             Slog.w(TAG, "error recovering builder", e);
         }
-
         return false;
     }
 
@@ -5938,12 +6151,17 @@
                     Slog.v(TAG, "INTERRUPTIVENESS: "
                             + record.getKey() + " is not interruptive: summary");
                 }
+            } else if (record.canBubble()) {
+                if (DEBUG_INTERRUPTIVENESS) {
+                    Slog.v(TAG, "INTERRUPTIVENESS: "
+                            + record.getKey() + " is not interruptive: bubble");
+                }
             } else {
+                record.setInterruptive(true);
                 if (DEBUG_INTERRUPTIVENESS) {
                     Slog.v(TAG, "INTERRUPTIVENESS: "
                             + record.getKey() + " is interruptive: alerted");
                 }
-                record.setInterruptive(true);
             }
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
@@ -6302,15 +6520,21 @@
             int indexBefore = findNotificationRecordIndexLocked(record);
             boolean interceptBefore = record.isIntercepted();
             int visibilityBefore = record.getPackageVisibilityOverride();
+            boolean interruptiveBefore = record.isInterruptive();
+
             recon.applyChangesLocked(record);
             applyZenModeLocked(record);
             mRankingHelper.sort(mNotificationList);
-            int indexAfter = findNotificationRecordIndexLocked(record);
-            boolean interceptAfter = record.isIntercepted();
-            int visibilityAfter = record.getPackageVisibilityOverride();
-            changed = indexBefore != indexAfter || interceptBefore != interceptAfter
-                    || visibilityBefore != visibilityAfter;
-            if (interceptBefore && !interceptAfter
+            boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
+            boolean interceptChanged = interceptBefore != record.isIntercepted();
+            boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
+
+            // Broadcast isInterruptive changes for bubbles.
+            boolean interruptiveChanged =
+                    record.canBubble() && (interruptiveBefore != record.isInterruptive());
+
+            changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
+            if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
                 buzzBeepBlinkLocked(record);
             }
@@ -7460,7 +7684,8 @@
                     record.getSound() != null || record.getVibration() != null,
                     record.getSystemGeneratedSmartActions(),
                     record.getSmartReplies(),
-                    record.canBubble()
+                    record.canBubble(),
+                    record.isInterruptive()
             );
             rankings.add(ranking);
         }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d580bd6..082b08deb 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -104,7 +104,7 @@
     @VisibleForTesting
     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
     private static final boolean DEFAULT_SHOW_BADGE = true;
-    private static final boolean DEFAULT_ALLOW_BUBBLE = true;
+    static final boolean DEFAULT_ALLOW_BUBBLE = true;
     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
 
@@ -134,7 +134,7 @@
     private final ZenModeHelper mZenModeHelper;
 
     private SparseBooleanArray mBadgingEnabled;
-    private SparseBooleanArray mBubblesEnabled;
+    private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE;
     private boolean mAreChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
@@ -1840,40 +1840,19 @@
     }
 
     public void updateBubblesEnabled() {
-        if (mBubblesEnabled == null) {
-            mBubblesEnabled = new SparseBooleanArray();
-        }
-        boolean changed = false;
-        // update the cached values
-        for (int index = 0; index < mBubblesEnabled.size(); index++) {
-            int userId = mBubblesEnabled.keyAt(index);
-            final boolean oldValue = mBubblesEnabled.get(userId);
-            final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.NOTIFICATION_BUBBLES,
-                    DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
-            mBubblesEnabled.put(userId, newValue);
-            changed |= oldValue != newValue;
-        }
-        if (changed) {
+        final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES,
+                DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
+        if (newValue != mBubblesEnabled) {
+            mBubblesEnabled = newValue;
             updateConfig();
         }
     }
 
-    public boolean bubblesEnabled(UserHandle userHandle) {
-        int userId = userHandle.getIdentifier();
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
-        if (mBubblesEnabled.indexOfKey(userId) < 0) {
-            mBubblesEnabled.put(userId,
-                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.NOTIFICATION_BUBBLES,
-                            DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
-        }
-        return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
+    public boolean bubblesEnabled() {
+        return mBubblesEnabled;
     }
 
-
     public void updateBadgingEnabled() {
         if (mBadgingEnabled == null) {
             mBadgingEnabled = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 5de00e4..7816f36 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -30,7 +30,7 @@
     boolean canShowBadge(String packageName, int uid);
     boolean badgingEnabled(UserHandle userHandle);
     boolean areBubblesAllowed(String packageName, int uid);
-    boolean bubblesEnabled(UserHandle userHandle);
+    boolean bubblesEnabled();
     boolean isGroupBlocked(String packageName, int uid, String groupId);
 
     Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index c6af756..8f05636 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -179,6 +179,7 @@
             case TYPE_SUPPRESSOR_CHANGED: return "suppressor_changed";
             case TYPE_LISTENER_HINTS_CHANGED: return "listener_hints_changed";
             case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
+            case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
             default: return "unknown";
         }
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ee948b2..f63aa52 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -954,12 +954,11 @@
     }
 
     private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
-        if (rule.zenMode == NotificationManager.INTERRUPTION_FILTER_NONE) {
+        if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             policy.apply(new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .build());
-        } else if (rule.zenMode
-                == NotificationManager.INTERRUPTION_FILTER_ALARMS) {
+        } else if (rule.zenMode == Global.ZEN_MODE_ALARMS) {
             policy.apply(new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .allowAlarms(true)
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 965ddc9..f8b3fb2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -744,7 +744,7 @@
                 @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
                 @NonNull final String[] args, @NonNull final ShellCallback callback,
                 @NonNull final ResultReceiver resultReceiver) {
-            (new OverlayManagerShellCommand(this)).exec(
+            (new OverlayManagerShellCommand(getContext(), this)).exec(
                     this, in, out, err, args, callback, resultReceiver);
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 99f5839..eb43275 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -18,15 +18,23 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.util.TypedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Implementation of 'cmd overlay' commands.
@@ -36,9 +44,11 @@
  * for a list of available commands.
  */
 final class OverlayManagerShellCommand extends ShellCommand {
+    private final Context mContext;
     private final IOverlayManager mInterface;
 
-    OverlayManagerShellCommand(@NonNull final IOverlayManager iom) {
+    OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
+        mContext = ctx;
         mInterface = iom;
     }
 
@@ -60,6 +70,8 @@
                     return runEnableExclusive();
                 case "set-priority":
                     return runSetPriority();
+                case "lookup":
+                    return runLookup();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -102,6 +114,10 @@
         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
         out.println("    If PARENT is the special keyword 'highest', change priority of");
         out.println("    PACKAGE to the highest priority.");
+        out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
+        out.println("    Load a package and print the value of a given resource");
+        out.println("    applying the current configuration and enabled overlays.");
+        out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
     }
 
     private int runList() throws RemoteException {
@@ -253,4 +269,92 @@
             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
         }
     }
+
+    private int runLookup() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        final PrintWriter err = getErrPrintWriter();
+
+        final boolean verbose = "--verbose".equals(getNextOption());
+
+        final String packageToLoad = getNextArgRequired();
+
+        final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
+        final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
+        final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
+        if (!matcher.matches()) {
+            err.println("Error: bad resource name, doesn't match package:type/name");
+            return 1;
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final Resources res;
+        try {
+            res = pm.getResourcesForApplication(packageToLoad);
+        } catch (PackageManager.NameNotFoundException e) {
+            err.println("Error: failed to get resources for package " + packageToLoad);
+            return 1;
+        }
+        final AssetManager assets = res.getAssets();
+        try {
+            assets.setResourceResolutionLoggingEnabled(true);
+
+            // first try as non-complex type ...
+            try {
+                final TypedValue value = new TypedValue();
+                res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
+                final CharSequence valueString = value.coerceToString();
+                final String resolution = assets.getLastResourceResolution();
+
+                res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
+                final CharSequence resolvedString = value.coerceToString();
+
+                if (verbose) {
+                    out.println(resolution);
+                }
+
+                if (valueString.equals(resolvedString)) {
+                    out.println(valueString);
+                } else {
+                    out.println(valueString + " -> " + resolvedString);
+                }
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // this is ok, resource could still be a complex type
+            }
+
+            // ... then try as complex type
+            try {
+
+                final String pkg = matcher.group(1);
+                final String type = matcher.group(2);
+                final String name = matcher.group(3);
+                final int resid = res.getIdentifier(name, type, pkg);
+                if (resid == 0) {
+                    throw new Resources.NotFoundException();
+                }
+                final TypedArray array = res.obtainTypedArray(resid);
+                if (verbose) {
+                    out.println(assets.getLastResourceResolution());
+                }
+                TypedValue tv = new TypedValue();
+                for (int i = 0; i < array.length(); i++) {
+                    array.getValue(i, tv);
+                    out.println(tv.coerceToString());
+                }
+                array.recycle();
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // give up
+                err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
+                return 1;
+            }
+        } finally {
+            assets.setResourceResolutionLoggingEnabled(false);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 7eb7438..61ea84f 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,13 +16,18 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageParser.Component;
+import static android.content.pm.PackageParser.IntentInfo;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
 import android.Manifest;
 import android.annotation.Nullable;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -34,10 +39,12 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.FgThread;
 import com.android.server.compat.PlatformCompat;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -49,19 +56,16 @@
  * The entity responsible for filtering visibility between apps based on declarations in their
  * manifests.
  */
-class AppsFilter {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class AppsFilter {
 
     private static final String TAG = PackageManagerService.TAG;
 
-    // Forces filtering logic to run for debug purposes.
-    // STOPSHIP (b/136675067): should be false after development is complete
-    private static final boolean DEBUG_RUN_WHEN_DISABLED = true;
-
     // Logs all filtering instead of enforcing
     private static final boolean DEBUG_ALLOW_ALL = false;
 
     @SuppressWarnings("ConstantExpression")
-    private static final boolean DEBUG_LOGGING = false | DEBUG_RUN_WHEN_DISABLED | DEBUG_ALLOW_ALL;
+    private static final boolean DEBUG_LOGGING = false | DEBUG_ALLOW_ALL;
 
     /**
      * This contains a list of packages that are implicitly queryable because another app explicitly
@@ -122,15 +126,13 @@
 
         /** @return true if the feature is enabled for the given package. */
         boolean packageIsEnabled(PackageParser.Package pkg);
+
     }
 
     private static class FeatureConfigImpl implements FeatureConfig {
         private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
-
-        // STOPSHIP(patb): set this to true if we plan to launch this in R
-        private static final boolean DEFAULT_ENABLED_STATE = false;
         private final PackageManagerService.Injector mInjector;
-        private volatile boolean mFeatureEnabled = DEFAULT_ENABLED_STATE;
+        private volatile boolean mFeatureEnabled = true;
 
         private FeatureConfigImpl(PackageManagerService.Injector injector) {
             mInjector = injector;
@@ -140,13 +142,13 @@
         public void onSystemReady() {
             mFeatureEnabled = DeviceConfig.getBoolean(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
-                    DEFAULT_ENABLED_STATE);
+                    true);
             DeviceConfig.addOnPropertiesChangedListener(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
                     properties -> {
                         synchronized (FeatureConfigImpl.this) {
                             mFeatureEnabled = properties.getBoolean(
-                                    FILTERING_ENABLED_NAME, DEFAULT_ENABLED_STATE);
+                                    FILTERING_ENABLED_NAME, true);
                         }
                     });
         }
@@ -200,12 +202,38 @@
             return false;
         }
         for (Intent intent : querying.mQueriesIntents) {
-            for (PackageParser.Activity activity : potentialTarget.activities) {
-                if (activity.intents != null) {
-                    for (PackageParser.ActivityIntentInfo filter : activity.intents) {
-                        if (matches(intent, filter)) {
-                            return true;
-                        }
+            if (matches(intent, potentialTarget.providers, potentialTarget.activities,
+                    potentialTarget.services, potentialTarget.receivers)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean matches(Intent intent,
+            ArrayList<PackageParser.Provider> providerList,
+            ArrayList<? extends Component<? extends IntentInfo>>... componentLists) {
+        for (int p = providerList.size() - 1; p >= 0; p--) {
+            PackageParser.Provider provider = providerList.get(p);
+            final ProviderInfo providerInfo = provider.info;
+            final Uri data = intent.getData();
+            if ("content".equalsIgnoreCase(intent.getScheme())
+                    && data != null
+                    && providerInfo.authority.equalsIgnoreCase(data.getAuthority())) {
+                return true;
+            }
+        }
+
+        for (int l = componentLists.length - 1; l >= 0; l--) {
+            ArrayList<? extends Component<? extends IntentInfo>> components = componentLists[l];
+            for (int c = components.size() - 1; c >= 0; c--) {
+                Component<? extends IntentInfo> component = components.get(c);
+                ArrayList<? extends IntentInfo> intents = component.intents;
+                for (int i = intents.size() - 1; i >= 0; i--) {
+                    IntentFilter intentFilter = intents.get(i);
+                    if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
+                            intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
+                        return true;
                     }
                 }
             }
@@ -213,32 +241,26 @@
         return false;
     }
 
-    /** Returns true if the given intent matches the given filter. */
-    private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) {
-        return filter.match(intent.getAction(), intent.getType(), intent.getScheme(),
-                intent.getData(), intent.getCategories(), "AppsFilter") > 0;
-    }
-
     /**
-     * Marks that a package initiated an interaction with another package, granting visibility of
-     * the prior from the former.
+     * Grants access based on an interaction between a calling and target package, granting
+     * visibility of the caller from the target.
      *
-     * @param initiatingPackage the package initiating the interaction
+     * @param callingPackage    the package initiating the interaction
      * @param targetPackage     the package being interacted with and thus gaining visibility of the
      *                          initiating package.
      * @param userId            the user in which this interaction was taking place
      */
-    private void markAppInteraction(
-            PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) {
+    public void grantImplicitAccess(
+            String callingPackage, String targetPackage, int userId) {
         HashMap<String, Set<String>> currentUser = mImplicitlyQueryable.get(userId);
         if (currentUser == null) {
             currentUser = new HashMap<>();
             mImplicitlyQueryable.put(userId, currentUser);
         }
-        if (!currentUser.containsKey(targetPackage.pkg.packageName)) {
-            currentUser.put(targetPackage.pkg.packageName, new HashSet<>());
+        if (!currentUser.containsKey(targetPackage)) {
+            currentUser.put(targetPackage, new HashSet<>());
         }
-        currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName);
+        currentUser.get(targetPackage).add(callingPackage);
     }
 
     public void onSystemReady() {
@@ -327,10 +349,16 @@
     public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
             PackageSetting targetPkgSetting, int userId) {
         final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
-        if (!featureEnabled && !DEBUG_RUN_WHEN_DISABLED) {
+        if (!featureEnabled) {
+            if (DEBUG_LOGGING) {
+                Slog.d(TAG, "filtering disabled; skipped");
+            }
             return false;
         }
         if (callingUid < Process.FIRST_APPLICATION_UID) {
+            if (DEBUG_LOGGING) {
+                Slog.d(TAG, "filtering skipped; " + callingUid + " is system");
+            }
             return false;
         }
         if (callingSetting == null) {
@@ -342,8 +370,6 @@
             callingPkgSetting = (PackageSetting) callingSetting;
             if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting,
                     userId)) {
-                // TODO: actually base this on a start / launch (not just a query)
-                markAppInteraction(callingPkgSetting, targetPkgSetting, userId);
                 return false;
             }
         } else if (callingSetting instanceof SharedUserSetting) {
@@ -354,8 +380,6 @@
                     final PackageSetting packageSetting = packageSettings.valueAt(i);
                     if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting,
                             userId)) {
-                        // TODO: actually base this on a start / launch (not just a query)
-                        markAppInteraction(packageSetting, targetPkgSetting, userId);
                         return false;
                     }
                     if (callingPkgSetting == null && packageSetting.pkg != null) {
@@ -371,22 +395,12 @@
                 return true;
             }
         }
-        if (!featureEnabled) {
-            return false;
+
+        if (DEBUG_LOGGING) {
+            log(callingPkgSetting, targetPkgSetting,
+                    DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED");
         }
-        if (mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
-            if (DEBUG_LOGGING) {
-                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
-                        + targetPkgSetting.name + (DEBUG_ALLOW_ALL ? " ALLOWED" : "BLOCKED"));
-            }
-            return !DEBUG_ALLOW_ALL;
-        } else {
-            if (DEBUG_LOGGING) {
-                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
-                        + targetPkgSetting.name + " DISABLED");
-            }
-            return false;
-        }
+        return !DEBUG_ALLOW_ALL;
     }
 
     private boolean shouldFilterApplicationInternal(
@@ -394,41 +408,74 @@
         final String callingName = callingPkgSetting.pkg.packageName;
         final PackageParser.Package targetPkg = targetPkgSetting.pkg;
 
+        if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "DISABLED");
+            }
+            return false;
+        }
         // This package isn't technically installed and won't be written to settings, so we can
         // treat it as filtered until it's available again.
         if (targetPkg == null) {
+            if (DEBUG_LOGGING) {
+                Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
+            }
             return true;
         }
         final String targetName = targetPkg.packageName;
         if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "caller pre-R");
+            }
             return false;
         }
         if (isImplicitlyQueryableSystemApp(targetPkgSetting)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "implicitly queryable sys");
+            }
             return false;
         }
         if (targetPkg.mForceQueryable) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "manifest forceQueryable");
+            }
             return false;
         }
         if (mForceQueryable.contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "whitelist forceQueryable");
+            }
             return false;
         }
         if (mQueriesViaPackage.containsKey(callingName)
                 && mQueriesViaPackage.get(callingName).contains(
                 targetName)) {
             // the calling package has explicitly declared the target package; allow
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "queries package");
+            }
             return false;
         } else if (mQueriesViaIntent.containsKey(callingName)
                 && mQueriesViaIntent.get(callingName).contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "queries intent");
+            }
             return false;
         }
         if (mImplicitlyQueryable.get(userId) != null
                 && mImplicitlyQueryable.get(userId).containsKey(callingName)
                 && mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "implicitly queryable for user");
+            }
             return false;
         }
         if (callingPkgSetting.pkg.instrumentation.size() > 0) {
             for (int i = 0, max = callingPkgSetting.pkg.instrumentation.size(); i < max; i++) {
                 if (callingPkgSetting.pkg.instrumentation.get(i).info.targetPackage == targetName) {
+                    if (DEBUG_LOGGING) {
+                        log(callingPkgSetting, targetPkgSetting, "instrumentation");
+                    }
                     return false;
                 }
             }
@@ -437,6 +484,9 @@
             if (mPermissionManager.checkPermission(
                     Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId)
                     == PackageManager.PERMISSION_GRANTED) {
+                if (DEBUG_LOGGING) {
+                    log(callingPkgSetting, targetPkgSetting, "permission");
+                }
                 return false;
             }
         } catch (RemoteException e) {
@@ -445,6 +495,13 @@
         return true;
     }
 
+    private static void log(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting,
+            String description) {
+        Slog.wtf(TAG,
+                "interaction: " + callingPkgSetting.name + " -> " + targetPkgSetting.name + " "
+                        + description);
+    }
+
     private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) {
         return targetPkgSetting.isSystem() && (mSystemAppsQueryable
                 || mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName));
@@ -467,7 +524,9 @@
         for (int user : users) {
             pw.append("    User ").append(Integer.toString(user)).println(":");
             final HashMap<String, Set<String>> queryMapForUser = mImplicitlyQueryable.get(user);
-            dumpQueriesMap(pw, filteringPackageName, queryMapForUser, "      ");
+            if (queryMapForUser != null) {
+                dumpQueriesMap(pw, filteringPackageName, queryMapForUser, "      ");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index c712431..08e55d3 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -34,6 +34,8 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.StatsLog;
@@ -84,6 +86,12 @@
 
     // Used for calculating space threshold for downgrading unused apps.
     private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
+    private static final int DEFAULT_INACTIVE_APP_THRESHOLD_DAYS = 10;
+
+    private static final String DOWNGRADE_UNUSED_APPS_ENABLED = "downgrade_unused_apps_enabled";
+    private static final String INACTIVE_APP_THRESHOLD_DAYS = "inactive_app_threshold_days";
+    private static final String LOW_STORAGE_MULTIPLIER_FOR_DOWNGRADE =
+            "low_storage_threshold_multiplier_for_downgrade";
 
     /**
      * Set of failed packages remembered across job runs.
@@ -103,8 +111,6 @@
     private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
 
     private final File mDataDir = Environment.getDataDirectory();
-    private static final long mDowngradeUnusedAppsThresholdInMillis =
-            getDowngradeUnusedAppsThresholdInMillis();
 
     public static void schedule(Context context) {
         if (isBackgroundDexoptDisabled()) {
@@ -346,14 +352,14 @@
             // Only downgrade apps when space is low on device.
             // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
             // up disk before user hits the actual lowStorageThreshold.
-            final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
+            final long lowStorageThresholdForDowngrade = getLowThresholdMultiplierForDowngrade()
                     * lowStorageThreshold;
             boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
             Log.d(TAG, "Should Downgrade " + shouldDowngrade);
             if (shouldDowngrade) {
                 Set<String> unusedPackages =
-                        pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
-                Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
+                        pm.getUnusedPackages(getDowngradeUnusedAppsThresholdInMillis());
+                Log.d(TAG, "Unused Packages " +  String.join(",", unusedPackages));
 
                 if (!unusedPackages.isEmpty()) {
                     for (String pkg : unusedPackages) {
@@ -362,12 +368,9 @@
                             // Should be aborted by the scheduler.
                             return abortCode;
                         }
-                        if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) {
+                        if (downgradePackage(pm, pkg)) {
                             updatedPackages.add(pkg);
                         }
-                        if (supportSecondaryDex) {
-                            downgradePackage(pm, pkg, /*isForPrimaryDex*/ false);
-                        }
                     }
 
                     pkgs = new ArraySet<>(pkgs);
@@ -415,39 +418,45 @@
      * Try to downgrade the package to a smaller compilation filter.
      * eg. if the package is in speed-profile the package will be downgraded to verify.
      * @param pm PackageManagerService
-     * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
-     * @return true if the package was downgraded.
+     * @param pkg The package to be downgraded
+     * @return true if the package was downgraded
      */
-    private boolean downgradePackage(PackageManagerService pm, String pkg,
-            boolean isForPrimaryDex) {
+    private boolean downgradePackage(PackageManagerService pm, String pkg) {
         Log.d(TAG, "Downgrading " + pkg);
-        boolean dex_opt_performed = false;
+        boolean downgradedPrimary = false;
         int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
         int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
                 | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
                 | DexoptOptions.DEXOPT_DOWNGRADE;
+
         long package_size_before = getPackageSize(pm, pkg);
-
-        if (isForPrimaryDex) {
-            // This applies for system apps or if packages location is not a directory, i.e.
-            // monolithic install.
-            if (!pm.canHaveOatDir(pkg)) {
-                // For apps that don't have the oat directory, instead of downgrading,
-                // remove their compiler artifacts from dalvik cache.
-                pm.deleteOatArtifactsOfPackage(pkg);
-            } else {
-                dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
-            }
+        // An aggressive downgrade deletes the oat files.
+        boolean aggressive = false;
+        // This applies for system apps or if packages location is not a directory, i.e.
+        // monolithic install.
+        if (!pm.canHaveOatDir(pkg)) {
+            // For apps that don't have the oat directory, instead of downgrading,
+            // remove their compiler artifacts from dalvik cache.
+            pm.deleteOatArtifactsOfPackage(pkg);
+            aggressive = true;
+            downgradedPrimary = true;
         } else {
-            dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+            downgradedPrimary = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+
+            if (supportSecondaryDex()) {
+                performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+            }
         }
 
-        if (dex_opt_performed) {
+        // This metric aims to log the storage savings when downgrading.
+        // The way disk size is measured using getPackageSize only looks at the primary apks.
+        // Any logs that are due to secondary dex files will show 0% size reduction and pollute
+        // the metrics.
+        if (downgradedPrimary) {
             StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
-                    getPackageSize(pm, pkg), /*aggressive=*/ false);
+                    getPackageSize(pm, pkg), aggressive);
         }
-        return dex_opt_performed;
+        return downgradedPrimary;
     }
 
     private boolean supportSecondaryDex() {
@@ -471,7 +480,7 @@
      * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
      * @param pm An instance of PackageManagerService
      * @param pkg The package to be downgraded.
-     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
+     * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
      * @return true if the package was downgraded.
      */
     private boolean optimizePackage(PackageManagerService pm, String pkg,
@@ -588,12 +597,6 @@
         // the checks above. This check is not "live" - the value is determined by a background
         // restart with a period of ~1 minute.
         PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
-        if (pm.isStorageLow()) {
-            if (DEBUG_DEXOPT) {
-                Log.i(TAG, "Low storage, skipping this run");
-            }
-            return false;
-        }
 
         final ArraySet<String> pkgs = pm.getOptimizablePackages();
         if (pkgs.isEmpty()) {
@@ -643,17 +646,77 @@
     }
 
     private static long getDowngradeUnusedAppsThresholdInMillis() {
+        long defaultValue = Long.MAX_VALUE;
+        if (isDowngradeFeatureEnabled()) {
+            return getInactiveAppsThresholdMillis();
+        }
         final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
         String sysPropValue = SystemProperties.get(sysPropKey);
         if (sysPropValue == null || sysPropValue.isEmpty()) {
             Log.w(TAG, "SysProp " + sysPropKey + " not set");
-            return Long.MAX_VALUE;
+            return defaultValue;
         }
-        return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
+        try {
+            return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
+        } catch (NumberFormatException e) {
+            Log.w(TAG, "Couldn't parse long for pm.dexopt.downgrade_after_inactive_days: "
+                    + sysPropValue + ". Returning default value instead.");
+            return defaultValue;
+        }
     }
 
     private static boolean isBackgroundDexoptDisabled() {
         return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
                 false /* default */);
     }
+
+    private static boolean isDowngradeFeatureEnabled() {
+        // DeviceConfig enables the control of on device features via remotely configurable flags,
+        // compared to SystemProperties which is only a way of sharing info system-widely, but are
+        // not configurable on the server-side.
+        String downgradeUnusedAppsEnabledFlag =
+                DeviceConfig.getProperty(
+                        DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                        DOWNGRADE_UNUSED_APPS_ENABLED);
+        return !TextUtils.isEmpty(downgradeUnusedAppsEnabledFlag)
+                && Boolean.parseBoolean(downgradeUnusedAppsEnabledFlag);
+    }
+
+    private static long getInactiveAppsThresholdMillis() {
+        long defaultValue = TimeUnit.DAYS.toMillis(DEFAULT_INACTIVE_APP_THRESHOLD_DAYS);
+        String inactiveAppThresholdDaysFlag =
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                        INACTIVE_APP_THRESHOLD_DAYS);
+        if (!TextUtils.isEmpty(inactiveAppThresholdDaysFlag)) {
+            try {
+                return TimeUnit.DAYS.toMillis(Long.parseLong(inactiveAppThresholdDaysFlag));
+            } catch (NumberFormatException e) {
+                Log.w(TAG, "Couldn't parse long for " + INACTIVE_APP_THRESHOLD_DAYS + " flag: "
+                        + inactiveAppThresholdDaysFlag + ". Returning default value instead.");
+                return defaultValue;
+            }
+        }
+        return defaultValue;
+    }
+
+    private static int getLowThresholdMultiplierForDowngrade() {
+        int defaultValue = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE;
+        if (isDowngradeFeatureEnabled()) {
+            String lowStorageThresholdMultiplierFlag =
+                    DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                            LOW_STORAGE_MULTIPLIER_FOR_DOWNGRADE);
+            if (!TextUtils.isEmpty(lowStorageThresholdMultiplierFlag)) {
+                try {
+                    return Integer.parseInt(lowStorageThresholdMultiplierFlag);
+                } catch (NumberFormatException e) {
+                    Log.w(TAG, "Couldn't parse long for "
+                            + LOW_STORAGE_MULTIPLIER_FOR_DOWNGRADE + " flag: "
+                            + lowStorageThresholdMultiplierFlag
+                            + ". Returning default value instead.");
+                    return defaultValue;
+                }
+            }
+        }
+        return defaultValue;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 5eaddf9..9e04c4b 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -401,7 +401,7 @@
 
     @GuardedBy("mService.mLock")
     public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
-            int targetAppId, int instantAppId) {
+            int instantAppId, int targetAppId) {
         if (mInstalledInstantAppUids == null) {
             return;     // no instant apps installed; no need to grant
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9b6333d..3464cab 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -443,45 +443,39 @@
             if (isManagedProfileAdmin(user, appInfo.packageName)) {
                 return false;
             }
-            // If app does not have any components or any permissions, the app can legitimately
-            // have no icon so we do not show the synthetic activity.
-            return hasComponentsAndRequestsPermissions(appInfo.packageName);
-        }
-
-        private boolean hasComponentsAndRequestsPermissions(@NonNull String packageName) {
             final PackageManagerInternal pmInt =
                     LocalServices.getService(PackageManagerInternal.class);
-            final PackageParser.Package pkg = pmInt.getPackage(packageName);
+            final PackageParser.Package pkg = pmInt.getPackage(appInfo.packageName);
             if (pkg == null) {
                 // Should not happen, but we shouldn't be failing if it does
                 return false;
             }
-            if (ArrayUtils.isEmpty(pkg.requestedPermissions)) {
-                return false;
-            }
-            if (!hasApplicationDeclaredActivities(pkg)
-                    && ArrayUtils.isEmpty(pkg.receivers)
-                    && ArrayUtils.isEmpty(pkg.providers)
-                    && ArrayUtils.isEmpty(pkg.services)) {
-                return false;
-            }
-            return true;
+            // If app does not have any default enabled launcher activity or any permissions,
+            // the app can legitimately have no icon so we do not show the synthetic activity.
+            return requestsPermissions(pkg) && hasDefaultEnableLauncherActivity(
+                    appInfo.packageName);
         }
 
-        private boolean hasApplicationDeclaredActivities(@NonNull PackageParser.Package pkg) {
-            if (pkg.activities == null) {
-                return false;
+        private boolean requestsPermissions(@NonNull PackageParser.Package pkg) {
+            return !ArrayUtils.isEmpty(pkg.requestedPermissions);
+        }
+
+        private boolean hasDefaultEnableLauncherActivity(@NonNull String packageName) {
+            final PackageManagerInternal pmInt =
+                    LocalServices.getService(PackageManagerInternal.class);
+            final Intent matchIntent = new Intent(Intent.ACTION_MAIN);
+            matchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+            matchIntent.setPackage(packageName);
+            final List<ResolveInfo> infoList = pmInt.queryIntentActivities(matchIntent,
+                    PackageManager.MATCH_DISABLED_COMPONENTS, Binder.getCallingUid(),
+                    getCallingUserId());
+            final int size = infoList.size();
+            for (int i = 0; i < size; i++) {
+                if (infoList.get(i).activityInfo.enabled) {
+                    return true;
+                }
             }
-            if (ArrayUtils.isEmpty(pkg.activities)) {
-                return false;
-            }
-            // If it only contains synthetic AppDetailsActivity only, it means application does
-            // not have actual activity declared in manifest.
-            if (pkg.activities.size() == 1 && PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
-                    pkg.activities.get(0).className)) {
-                return false;
-            }
-            return true;
+            return false;
         }
 
         private boolean isManagedProfileAdmin(UserHandle user, String packageName) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b632d18f..8b24224 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -491,6 +491,7 @@
 
             params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
             params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
+            params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
             params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
             if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
                     && !mPm.isCallerVerifier(callingUid)) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2bf5f4d..123e65e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -37,7 +37,7 @@
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
 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;
@@ -113,6 +113,7 @@
 import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 
 import android.Manifest;
+import android.annotation.AppIdInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -378,14 +379,14 @@
  * <p>
  * Internally there are two important locks:
  * <ul>
- * <li>{@link #mPackages} is used to guard all in-memory parsed package details
+ * <li>{@link #mLock} is used to guard all in-memory parsed package details
  * and other related state. It is a fine-grained lock that should only be held
  * momentarily, as it's one of the most contended locks in the system.
  * <li>{@link #mInstallLock} is used to guard all {@code installd} access, whose
  * operations typically involve heavy lifting of application data on disk. Since
  * {@code installd} is single-threaded, and it's operations can often be slow,
- * this lock should never be acquired while already holding {@link #mPackages}.
- * Conversely, it's safe to acquire {@link #mPackages} momentarily while already
+ * this lock should never be acquired while already holding {@link #mLock}.
+ * Conversely, it's safe to acquire {@link #mLock} momentarily while already
  * holding {@link #mInstallLock}.
  * </ul>
  * Many internal methods rely on the caller to hold the appropriate locks, and
@@ -394,8 +395,8 @@
  * <li>fooLI(): the caller must hold {@link #mInstallLock}
  * <li>fooLIF(): the caller must hold {@link #mInstallLock} and the package
  * being modified must be frozen
- * <li>fooLPr(): the caller must hold {@link #mPackages} for reading
- * <li>fooLPw(): the caller must hold {@link #mPackages} for writing
+ * <li>fooLPr(): the caller must hold {@link #mLock} for reading
+ * <li>fooLPw(): the caller must hold {@link #mLock} for writing
  * </ul>
  * <p>
  * Because this class is very central to the platform's security; please run all
@@ -580,16 +581,6 @@
 
     private static final String PACKAGE_SCHEME = "package";
 
-    private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
-
-    private static final String PRODUCT_OVERLAY_DIR = "/product/overlay";
-
-    private static final String SYSTEM_EXT_OVERLAY_DIR = "/system_ext/overlay";
-
-    private static final String ODM_OVERLAY_DIR = "/odm/overlay";
-
-    private static final String OEM_OVERLAY_DIR = "/oem/overlay";
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -669,7 +660,7 @@
 
     // Lock for global state used when modifying package state or settings.
     // Methods that must be called with this lock held have
-    // the suffix "Locked". Some methods may use the legacy the suffix "LP"
+    // the suffix "Locked". Some methods may use the legacy suffix "LP"
     final Object mLock;
 
     // Keys are String (package name), values are Package.
@@ -755,6 +746,26 @@
     private final Injector mInjector;
 
     /**
+     * The list of all system partitions that may contain packages in ascending order of
+     * specificity (the more generic, the earlier in the list a partition appears).
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
+            Arrays.asList(
+                    new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
+                            true /* hasPriv */, false /* hasOverlays */),
+                    new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
+                            false /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
+                            true /* hasPriv */, true /* hasOverlays */)));
+
+    /**
      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
      *
      * NOTE: All getters should return the same instance for every call.
@@ -1581,7 +1592,7 @@
     private static final int USER_RUNTIME_GRANT_MASK =
             FLAG_PERMISSION_USER_SET
             | FLAG_PERMISSION_USER_FIXED
-            | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+            | FLAG_PERMISSION_REVOKED_COMPAT;
 
     final @Nullable String mRequiredVerifierPackage;
     final @NonNull String mRequiredInstallerPackage;
@@ -2149,6 +2160,14 @@
                     pkgList.add(packageName);
                     sendResourcesChangedBroadcast(true, true, pkgList, uidArray, null);
                 }
+            } else if (!ArrayUtils.isEmpty(res.libraryConsumers)) { // if static shared lib
+                for (int i = 0; i < res.libraryConsumers.size(); i++) {
+                    PackageParser.Package pkg = res.libraryConsumers.get(i);
+                    // send broadcast that all consumers of the static shared library have changed
+                    sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
+                            new ArrayList<>(Collections.singletonList(pkg.packageName)),
+                            pkg.applicationInfo.uid);
+                }
             }
 
             // Work that needs to happen on first install within each user
@@ -2543,6 +2562,51 @@
         }
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static class SystemPartition {
+        public final File folder;
+        public final int scanFlag;
+        public final File appFolder;
+        @Nullable
+        public final File privAppFolder;
+        @Nullable
+        public final File overlayFolder;
+
+        private SystemPartition(File folder, int scanFlag, boolean hasPrivApps,
+                boolean hasOverlays) {
+            this.folder = folder;
+            this.scanFlag = scanFlag;
+            this.appFolder = toCanonical(new File(folder, "app"));
+            this.privAppFolder = hasPrivApps ? toCanonical(new File(folder, "priv-app")) : null;
+            this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
+        }
+
+        public boolean containsPrivApp(File scanFile) {
+            return FileUtils.contains(privAppFolder, scanFile);
+        }
+
+        public boolean containsApp(File scanFile) {
+            return FileUtils.contains(appFolder, scanFile);
+        }
+
+        public boolean containsPath(String path) {
+            return path.startsWith(folder.getPath() + "/");
+        }
+
+        public boolean containsPrivPath(String path) {
+            return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
+        }
+
+        private static File toCanonical(File dir) {
+            try {
+                return dir.getCanonicalFile();
+            } catch (IOException e) {
+                // failed to look up canonical path, continue with original one
+                return dir;
+            }
+        }
+    }
+
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
@@ -2766,215 +2830,35 @@
             // any apps.)
             // For security and version matching reason, only consider overlay packages if they
             // reside in the right directory.
-            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-            scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-            scanDirTracedLI(new File(SYSTEM_EXT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
-            scanDirTracedLI(new File(ODM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_ODM,
-                    0);
-            scanDirTracedLI(new File(OEM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
+            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+            for (int i = SYSTEM_PARTITIONS.size() - 1; i >= 0; i--) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.overlayFolder == null) {
+                    continue;
+                }
+                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
+            }
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
 
-            // Find base frameworks (resource packages without code).
-            scanDirTracedLI(frameworkDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_NO_DEX
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
+            scanDirTracedLI(frameworkDir, systemParseFlags,
+                    systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
             if (!mPackages.containsKey("android")) {
                 throw new IllegalStateException(
                         "Failed to load frameworks package; check log for warnings");
             }
-
-            // Collect privileged system packages.
-            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            scanDirTracedLI(privilegedAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary system packages.
-            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
-            scanDirTracedLI(systemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM,
-                    0);
-
-            // Collect privileged vendor packages.
-            File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
-            try {
-                privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
+            for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.privAppFolder != null) {
+                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
+                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
+                }
+                scanDirTracedLI(partition.appFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
             }
-            scanDirTracedLI(privilegedVendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
 
-            // Collect ordinary vendor packages.
-            File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
-            try {
-                vendorAppDir = vendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(vendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect privileged odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File privilegedOdmAppDir = new File(Environment.getOdmDirectory(),
-                        "priv-app");
-            try {
-                privilegedOdmAppDir = privilegedOdmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedOdmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File odmAppDir = new File(Environment.getOdmDirectory(), "app");
-            try {
-                odmAppDir = odmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(odmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect all OEM packages.
-            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
-            scanDirTracedLI(oemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
-
-            // Collected privileged /product packages.
-            File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
-            try {
-                privilegedProductAppDir = privilegedProductAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedProductAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /product packages.
-            File productAppDir = new File(Environment.getProductDirectory(), "app");
-            try {
-                productAppDir = productAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(productAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-
-            // Collected privileged /system_ext packages.
-            File privilegedSystemExtAppDir =
-                    new File(Environment.getSystemExtDirectory(), "priv-app");
-            try {
-                privilegedSystemExtAppDir =
-                        privilegedSystemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedSystemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /system_ext packages.
-            File systemExtAppDir = new File(Environment.getSystemExtDirectory(), "app");
-            try {
-                systemExtAppDir = systemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(systemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
 
             // Prune any system packages that no longer exist.
             final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -3142,89 +3026,26 @@
                         logCriticalInfo(Log.WARN, "Expected better " + packageName
                                 + " but never showed up; reverting to system");
 
-                        final @ParseFlags int reparseFlags;
-                        final @ScanFlags int rescanFlags;
-                        if (FileUtils.contains(privilegedAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM;
-                        } else if (FileUtils.contains(privilegedVendorAppDir, scanFile)
-                                || FileUtils.contains(privilegedOdmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(vendorAppDir, scanFile)
-                                || FileUtils.contains(odmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR;
-                        } else if (FileUtils.contains(oemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_OEM;
-                        } else if (FileUtils.contains(privilegedProductAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(productAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT;
-                        } else if (FileUtils.contains(privilegedSystemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT;
-                        } else {
+                        @ParseFlags int reparseFlags = 0;
+                        @ScanFlags int rescanFlags = 0;
+                        for (int i1 = 0, size = SYSTEM_PARTITIONS.size(); i1 < size; i1++) {
+                            SystemPartition partition = SYSTEM_PARTITIONS.get(i1);
+                            if (partition.containsPrivApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
+                                        | partition.scanFlag;
+                                break;
+                            }
+                            if (partition.containsApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | partition.scanFlag;
+                                break;
+                            }
+                        }
+                        if (rescanFlags == 0) {
                             Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                             continue;
                         }
-
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
@@ -6523,13 +6344,13 @@
     }
 
     // TODO: handle preferred activities missing while user has amnesia
-    /** <b>must not hold {@link #mPackages}</b> */
+    /** <b>must not hold {@link #mLock}</b> */
     ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
             List<ResolveInfo> query, int priority, boolean always,
             boolean removeMatches, boolean debug, int userId) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
@@ -8366,15 +8187,20 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
-        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        final int callingUid = Binder.getCallingUid();
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isInstantApp");
+
+        return isInstantAppInternal(packageName, userId, callingUid);
+    }
+
+    private boolean isInstantAppInternal(String packageName, @UserIdInt int userId,
+            int callingUid) {
         if (HIDE_EPHEMERAL_APIS) {
             return false;
         }
-
         synchronized (mLock) {
-            int callingUid = Binder.getCallingUid();
             if (Process.isIsolated(callingUid)) {
                 callingUid = mIsolatedOwners.get(callingUid);
             }
@@ -11878,6 +11704,9 @@
                 }
             }
         }
+        if (reconciledPkg.installResult != null) {
+            reconciledPkg.installResult.libraryConsumers = clientLibPkgs;
+        }
 
         if ((scanFlags & SCAN_BOOTING) != 0) {
             // No apps can run during boot scan, so they don't need to be frozen
@@ -12048,11 +11877,12 @@
                 | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
     }
 
-    private void killApplication(String pkgName, int appId, String reason) {
+    private void killApplication(String pkgName, @AppIdInt int appId, String reason) {
         killApplication(pkgName, appId, UserHandle.USER_ALL, reason);
     }
 
-    private void killApplication(String pkgName, int appId, int userId, String reason) {
+    private void killApplication(String pkgName, @AppIdInt int appId,
+            @UserIdInt int userId, String reason) {
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -12427,7 +12257,7 @@
 
     @Override
     public void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
-            boolean includeStopped, int appId, int[] userIds, int[] instantUserIds) {
+            boolean includeStopped, @AppIdInt int appId, int[] userIds, int[] instantUserIds) {
         if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
             return;
         }
@@ -13777,8 +13607,7 @@
     private void restoreAndPostInstall(
             int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
         if (DEBUG_INSTALL) {
-            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package="
-                    + res.pkg.packageName);
+            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.pkg);
         }
 
         // A restore should be performed at this point if (a) the install
@@ -13842,7 +13671,7 @@
 
         // If this is an update to a package that might be potentially downgraded, then we
         // need to check with the rollback manager whether there's any userdata that might
-        // need to be restored for the package.
+        // need to be snapshotted or restored for the package.
         //
         // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
         if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
@@ -13869,12 +13698,16 @@
                 installedUsers = ps.queryInstalledUsers(allUsers, true);
             }
 
-            if (ps != null) {
+            boolean doSnapshotOrRestore = data != null && data.args != null
+                    && ((data.args.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                    || (data.args.installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+
+            if (ps != null && doSnapshotOrRestore) {
                 try {
                     rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
                             seInfo, token);
                 } catch (RemoteException re) {
-                    // Cannot happen, the RollbackManager is hosted in the same process.
+                    Log.e(TAG, "Error snapshotting/restoring user data: " + re);
                 }
                 doRestore = true;
             }
@@ -15157,6 +14990,8 @@
         String installerPackageName;
         PackageRemovedInfo removedInfo;
         ArrayMap<String, PackageInstalledInfo> addedChildPackages;
+        // The set of packages consuming this shared library or null if no consumers exist.
+        ArrayList<PackageParser.Package> libraryConsumers;
 
         public void setError(int code, String msg) {
             setReturnCode(code);
@@ -16183,7 +16018,7 @@
     /**
      * On successful install, executes remaining steps after commit completes and the package lock
      * is released. These are typically more expensive or require calls to installd, which often
-     * locks on {@link #mPackages}.
+     * locks on {@link #mLock}.
      */
     private void executePostCommitSteps(CommitRequest commitRequest) {
         for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
@@ -17106,8 +16941,7 @@
                 if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
                     try {
                         VerityUtils.setUpFsverity(filePath, signaturePath);
-                    } catch (IOException | DigestException | NoSuchAlgorithmException
-                            | SecurityException e) {
+                    } catch (IOException e) {
                         throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
                                 "Failed to enable fs-verity: " + e);
                     }
@@ -17913,7 +17747,7 @@
             }
             if (removedAppId >= 0) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
-                    null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+                        removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                     null, null, broadcastUsers, instantUserIds);
             }
         }
@@ -18001,19 +17835,20 @@
                         // 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.
+                        boolean shouldKill = false;
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
                                     userId);
-                            if (userIdToKill == UserHandle.USER_ALL
-                                    || userIdToKill >= UserHandle.USER_SYSTEM) {
-                                // If gids changed for this user, kill all affected packages.
-                                mHandler.post(() -> {
-                                    // This has to happen with no lock held.
-                                    killApplication(deletedPs.name, deletedPs.appId,
-                                            KILL_APP_REASON_GIDS_CHANGED);
-                                });
-                                break;
-                            }
+                            shouldKill |= userIdToKill == UserHandle.USER_ALL
+                                    || userIdToKill >= UserHandle.USER_SYSTEM;
+                        }
+                        // If gids changed, kill all affected packages.
+                        if (shouldKill) {
+                            mHandler.post(() -> {
+                                // This has to happen with no lock held.
+                                killApplication(deletedPs.name, deletedPs.appId,
+                                        KILL_APP_REASON_GIDS_CHANGED);
+                            });
                         }
                     }
                     clearPackagePreferredActivitiesLPw(
@@ -18061,70 +17896,15 @@
     }
 
     static boolean locationIsPrivileged(String path) {
-        try {
-            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            final File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
-            final File privilegedOdmAppDir = new File(Environment.getOdmDirectory(), "priv-app");
-            final File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
-            final File privilegedSystemExtAppDir =
-                    new File(Environment.getSystemExtDirectory(), "priv-app");
-            return path.startsWith(privilegedAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedVendorAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedOdmAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedProductAppDir.getCanonicalPath() + "/")
-                    || path.startsWith(privilegedSystemExtAppDir.getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
+        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+            if (partition.containsPrivPath(path)) {
+                return true;
+            }
         }
         return false;
     }
 
-    static boolean locationIsOem(String path) {
-        try {
-            return path.startsWith(Environment.getOemDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsVendor(String path) {
-        try {
-            return path.startsWith(Environment.getVendorDirectory().getCanonicalPath() + "/")
-                    || path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsProduct(String path) {
-        try {
-            return path.startsWith(Environment.getProductDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsSystemExt(String path) {
-        try {
-            return path.startsWith(
-              Environment.getSystemExtDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
-
-    static boolean locationIsOdm(String path) {
-        try {
-            return path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
-        } catch (IOException e) {
-            Slog.e(TAG, "Unable to access code path " + path);
-        }
-        return false;
-    }
 
     /*
      * Tries to delete system package.
@@ -18235,23 +18015,15 @@
                 | PackageParser.PARSE_MUST_BE_APK
                 | PackageParser.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
-        if (locationIsPrivileged(codePathString)) {
-            scanFlags |= SCAN_AS_PRIVILEGED;
-        }
-        if (locationIsOem(codePathString)) {
-            scanFlags |= SCAN_AS_OEM;
-        }
-        if (locationIsVendor(codePathString)) {
-            scanFlags |= SCAN_AS_VENDOR;
-        }
-        if (locationIsProduct(codePathString)) {
-            scanFlags |= SCAN_AS_PRODUCT;
-        }
-        if (locationIsSystemExt(codePathString)) {
-            scanFlags |= SCAN_AS_SYSTEM_EXT;
-        }
-        if (locationIsOdm(codePathString)) {
-            scanFlags |= SCAN_AS_ODM;
+        for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+            SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+            if (partition.containsPath(codePathString)) {
+                scanFlags |= partition.scanFlag;
+                if (partition.containsPrivPath(codePathString)) {
+                    scanFlags |= SCAN_AS_PRIVILEGED;
+                }
+                break;
+            }
         }
 
         final File codePath = new File(codePathString);
@@ -18886,7 +18658,8 @@
      * Remove entries from the keystore daemon. Will only remove it if the
      * {@code appId} is valid.
      */
-    private static void removeKeystoreDataIfNeeded(UserManagerInternal um, int userId, int appId) {
+    private static void removeKeystoreDataIfNeeded(UserManagerInternal um, @UserIdInt int userId,
+            @AppIdInt int appId) {
         if (appId < 0) {
             return;
         }
@@ -18972,7 +18745,7 @@
     }
 
     @Override
-    public void getPackageSizeInfo(final String packageName, int userHandle,
+    public void getPackageSizeInfo(final String packageName, int userId,
             final IPackageStatsObserver observer) {
         throw new UnsupportedOperationException(
                 "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!");
@@ -19767,7 +19540,7 @@
 
     public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) {
         UserManagerService ums = UserManagerService.getInstance();
-        if (ums != null) {
+        if (ums != null && !sessionInfo.isStaged()) {
             final UserInfo parent = ums.getProfileParent(userId);
             final int launcherUid = (parent != null) ? parent.id : userId;
             final ComponentName launcherComponent = getDefaultHomeActivity(launcherUid);
@@ -19860,11 +19633,11 @@
         return null;
     }
 
-    /** <b>must not hold {@link #mPackages}</b> */
+    /** <b>must not hold {@link #mLock}</b> */
     private void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         for (int i = userIds.size() - 1; i >= 0; --i) {
             final int userId = userIds.keyAt(i);
@@ -19873,14 +19646,14 @@
     }
 
     /**
-     * <b>must not hold {@link #mPackages}</b>
+     * <b>must not hold {@link #mLock}</b>
      *
      * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
      */
     private boolean updateDefaultHomeNotLocked(int userId) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         if (!mSystemReady) {
             // We might get called before system is ready because of package changes etc, but
@@ -21811,7 +21584,7 @@
      * will try recovering system apps by wiping data; third-party app data is
      * left intact.
      * <p>
-     * <em>Note: To avoid a deadlock, do not call this method with {@code mPackages} lock held</em>
+     * <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
      */
     private void prepareAppDataAfterInstallLIF(PackageParser.Package pkg) {
         final PackageSetting ps;
@@ -22477,24 +22250,24 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUser(UserManagerService userManager, int userHandle) {
+    void cleanUpUser(UserManagerService userManager, @UserIdInt int userId) {
         synchronized (mLock) {
-            mDirtyUsers.remove(userHandle);
-            mUserNeedsBadging.delete(userHandle);
-            mSettings.removeUserLPw(userHandle);
-            mPendingBroadcasts.remove(userHandle);
-            mInstantAppRegistry.onUserRemovedLPw(userHandle);
-            removeUnusedPackagesLPw(userManager, userHandle);
+            mDirtyUsers.remove(userId);
+            mUserNeedsBadging.delete(userId);
+            mSettings.removeUserLPw(userId);
+            mPendingBroadcasts.remove(userId);
+            mInstantAppRegistry.onUserRemovedLPw(userId);
+            removeUnusedPackagesLPw(userManager, userId);
         }
     }
 
     /**
-     * We're removing userHandle and would like to remove any downloaded packages
+     * We're removing userId and would like to remove any downloaded packages
      * that are no longer in use by any other user.
-     * @param userHandle the user being removed
+     * @param userId the user being removed
      */
     @GuardedBy("mLock")
-    private void removeUnusedPackagesLPw(UserManagerService userManager, final int userHandle) {
+    private void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
         final boolean DEBUG_CLEAN_APKS = false;
         int [] users = userManager.getUserIds();
         Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
@@ -22518,7 +22291,7 @@
                 }
             } else {
                 for (int i = 0; i < users.length; i++) {
-                    if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                    if (users[i] != userId && ps.getInstalled(users[i])) {
                         keep = true;
                         if (DEBUG_CLEAN_APKS) {
                             Slog.i(TAG, "  Keeping package " + packageName + " for user "
@@ -22534,7 +22307,7 @@
                 }
                 //end run
                 mHandler.post(() -> deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
-                        userHandle, 0));
+                        userId, 0));
             }
         }
     }
@@ -23360,11 +23133,25 @@
         }
 
         @Override
-        public void grantEphemeralAccess(int userId, Intent intent,
-                int targetAppId, int ephemeralAppId) {
+        public void grantImplicitAccess(int userId, Intent intent,
+                int callingUid, int targetAppId) {
             synchronized (mLock) {
-                mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
-                        targetAppId, ephemeralAppId);
+                final PackageParser.Package callingPackage = getPackage(callingUid);
+                final PackageParser.Package targetPackage =
+                        getPackage(UserHandle.getUid(userId, targetAppId));
+                if (callingPackage == null || targetPackage == null) {
+                    return;
+                }
+
+                final boolean instantApp = isInstantAppInternal(callingPackage.packageName, userId,
+                        callingUid);
+                if (instantApp) {
+                    mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+                            UserHandle.getAppId(callingUid), targetAppId);
+                } else {
+                    mAppsFilter.grantImplicitAccess(
+                            callingPackage.packageName, targetPackage.packageName, userId);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e8090e3..fe529a1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -94,9 +94,11 @@
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
+import android.util.SparseArray;
 
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 
@@ -274,7 +276,7 @@
                 case "get-harmful-app-warning":
                     return runGetHarmfulAppWarning();
                 case "get-stagedsessions":
-                    return getStagedSessions();
+                    return runListStagedSessions();
                 case "uninstall-system-updates":
                     return uninstallSystemUpdates();
                 case "rollback-app":
@@ -346,28 +348,6 @@
         return 1;
     }
 
-    private int getStagedSessions() {
-        final PrintWriter pw = getOutPrintWriter();
-        try {
-            List<SessionInfo> stagedSessionsList =
-                    mInterface.getPackageInstaller().getStagedSessions().getList();
-            for (SessionInfo session: stagedSessionsList) {
-                pw.println("appPackageName = " + session.getAppPackageName()
-                        + "; sessionId = " + session.getSessionId()
-                        + "; isStaged = " + session.isStaged()
-                        + "; isStagedSessionReady = " + session.isStagedSessionReady()
-                        + "; isStagedSessionApplied = " + session.isStagedSessionApplied()
-                        + "; isStagedSessionFailed = " + session.isStagedSessionFailed() + ";");
-            }
-        } catch (RemoteException e) {
-            pw.println("Failure ["
-                    + e.getClass().getName() + " - "
-                    + e.getMessage() + "]");
-            return 0;
-        }
-        return 1;
-    }
-
     private int uninstallSystemUpdates() {
         final PrintWriter pw = getOutPrintWriter();
         List<String> failedUninstalls = new LinkedList<>();
@@ -540,6 +520,8 @@
                 return runListPermissionGroups();
             case "permissions":
                 return runListPermissions();
+            case "staged-sessions":
+                return runListStagedSessions();
             case "users":
                 ServiceManager.getService("user").shellCommand(
                         getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
@@ -877,6 +859,103 @@
         return 0;
     }
 
+    private static class SessionDump {
+        boolean onlyParent; // Show parent sessions only
+        boolean onlyReady; // Show only staged sessions that are in ready state
+        boolean onlySessionId; // Show sessionId only
+    }
+
+    // Returns true if the provided flag is a session flag and given SessionDump was updated
+    private boolean setSessionFlag(String flag, SessionDump sessionDump) {
+        switch (flag) {
+            case "--only-parent":
+                sessionDump.onlyParent = true;
+                break;
+            case "--only-ready":
+                sessionDump.onlyReady = true;
+                break;
+            case "--only-sessionid":
+                sessionDump.onlySessionId = true;
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private int runListStagedSessions() {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(
+                getOutPrintWriter(), /* singleIndent */ "  ", /* wrapLength */ 120);
+
+        SessionDump sessionDump = new SessionDump();
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (!setSessionFlag(opt, sessionDump)) {
+                pw.println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+
+        try {
+            List<SessionInfo> stagedSessions =
+                    mInterface.getPackageInstaller().getStagedSessions().getList();
+            printSessionList(pw, stagedSessions, sessionDump);
+        } catch (RemoteException e) {
+            pw.println("Failure ["
+                    + e.getClass().getName() + " - "
+                    + e.getMessage() + "]");
+            return -1;
+        }
+        return 1;
+    }
+
+    private void printSessionList(IndentingPrintWriter pw, List<SessionInfo> stagedSessions,
+            SessionDump sessionDump) {
+        final SparseArray<SessionInfo> sessionById = new SparseArray<>(stagedSessions.size());
+        for (SessionInfo session : stagedSessions) {
+            sessionById.put(session.getSessionId(), session);
+        }
+        for (SessionInfo session: stagedSessions) {
+            if (sessionDump.onlyReady && !session.isStagedSessionReady()) {
+                continue;
+            }
+            if (session.getParentSessionId() != SessionInfo.INVALID_ID) {
+                continue;
+            }
+            printSession(pw, session, sessionDump);
+            if (session.isMultiPackage() && !sessionDump.onlyParent) {
+                pw.increaseIndent();
+                final int[] childIds = session.getChildSessionIds();
+                for (int i = 0; i < childIds.length; i++) {
+                    final SessionInfo childSession = sessionById.get(childIds[i]);
+                    if (childSession == null) {
+                        if (sessionDump.onlySessionId) {
+                            pw.println(childIds[i]);
+                        } else {
+                            pw.println("sessionId = " + childIds[i] + "; not found");
+                        }
+                    } else {
+                        printSession(pw, childSession, sessionDump);
+                    }
+                }
+                pw.decreaseIndent();
+            }
+        }
+    }
+
+    private static void printSession(PrintWriter pw, SessionInfo session, SessionDump sessionDump) {
+        if (sessionDump.onlySessionId) {
+            pw.println(session.getSessionId());
+            return;
+        }
+        pw.println("sessionId = " + session.getSessionId()
+                + "; appPackageName = " + session.getAppPackageName()
+                + "; isStaged = " + session.isStaged()
+                + "; isReady = " + session.isStagedSessionReady()
+                + "; isApplied = " + session.isStagedSessionApplied()
+                + "; isFailed = " + session.isStagedSessionFailed() + ";");
+    }
+
     private Intent parseIntentAndUser() throws URISyntaxException {
         mTargetUser = UserHandle.USER_CURRENT;
         mBrief = false;
@@ -3081,6 +3160,12 @@
         pw.println("      -d: only list dangerous permissions");
         pw.println("      -u: list only the permissions users will see");
         pw.println("");
+        pw.println("  list staged-sessions [--only-ready] [--only-sessionid] [--only-parent]");
+        pw.println("    Displays list of all staged sessions on device.");
+        pw.println("      --only-ready: show only staged sessions that are ready");
+        pw.println("      --only-sessionid: show only sessionId of each session");
+        pw.println("      --only-parent: hide all children sessions");
+        pw.println("");
         pw.println("  resolve-activity [--brief] [--components] [--query-flags FLAGS]");
         pw.println("       [--user USER_ID] INTENT");
         pw.println("    Prints the activity that resolves to the given INTENT.");
@@ -3318,7 +3403,7 @@
         pw.println("  uninstall-system-updates");
         pw.println("    Remove updates to all system applications and fall back to their /system " +
                 "version.");
-        pw.println();
+        pw.println("");
         pw.println("  get-moduleinfo [--all | --installed] [module-name]");
         pw.println("    Displays module info. If module-name is specified only that info is shown");
         pw.println("    By default, without any argument only installed modules are shown.");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 5972468..1873a4e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -62,6 +62,7 @@
 import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -94,6 +95,7 @@
 import com.android.server.pm.permission.PermissionSettings;
 import com.android.server.pm.permission.PermissionsState;
 import com.android.server.pm.permission.PermissionsState.PermissionState;
+import com.android.server.utils.TimingsTraceAndSlog;
 
 import libcore.io.IoUtils;
 
@@ -4012,8 +4014,11 @@
         }
     }
 
-    void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
-            int userHandle, String[] disallowedPackages) {
+    void createNewUserLI(@NonNull PackageManagerService service,
+            @NonNull Installer installer, int userHandle, String[] disallowedPackages) {
+        final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
+                Trace.TRACE_TAG_PACKAGE_MANAGER);
+        t.traceBegin("createNewUser-" + userHandle);
         String[] volumeUuids;
         String[] names;
         int[] appIds;
@@ -4051,6 +4056,7 @@
                 targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
             }
         }
+        t.traceBegin("createAppData");
         for (int i = 0; i < packagesCount; i++) {
             if (names[i] == null) {
                 continue;
@@ -4064,9 +4070,11 @@
                 Slog.w(TAG, "Failed to prepare app data", e);
             }
         }
+        t.traceEnd(); // createAppData
         synchronized (mLock) {
             applyDefaultPreferredAppsLPw(userHandle);
         }
+        t.traceEnd(); // createNewUser
     }
 
     void removeUserLPw(int userId) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index dd1eb83..6b4ef69 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -39,6 +39,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -58,6 +60,7 @@
 import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -72,7 +75,7 @@
     private final PackageInstallerService mPi;
     private final ApexManager mApexManager;
     private final PowerManager mPowerManager;
-    private final Handler mBgHandler;
+    private final PreRebootVerificationHandler mPreRebootVerificationHandler;
 
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
@@ -81,7 +84,8 @@
         mPi = pi;
         mApexManager = am;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mBgHandler = BackgroundThread.getHandler();
+        mPreRebootVerificationHandler = new PreRebootVerificationHandler(
+                BackgroundThread.get().getLooper());
     }
 
     private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -150,6 +154,14 @@
             return;
         }
 
+        // Verify signing details for downgrade
+        // Allow downgrading from B to A iff it is possible to upgrade from A to B
+        if (existingApexPkg.getLongVersionCode() > newApexPkg.getLongVersionCode()
+                && existingSigningDetails.checkCapability(signingDetails,
+                        PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) {
+            return;
+        }
+
         throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                 "APK-container signature of APEX package " + packageName + " with version "
                         + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
@@ -241,75 +253,6 @@
         return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
     }
 
-    private void preRebootVerification(@NonNull PackageInstallerSession session) {
-        Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
-        final boolean hasApex = sessionContainsApex(session);
-        // APEX checks. For single-package sessions, check if they contain an APEX. For
-        // multi-package sessions, find all the child sessions that contain an APEX.
-        if (hasApex) {
-            try {
-                final List<PackageInfo> apexPackages = submitSessionToApexService(session);
-                for (PackageInfo apexPackage : apexPackages) {
-                    validateApexSignature(apexPackage, session.params.installFlags);
-                }
-            } catch (PackageManagerException e) {
-                session.setStagedSessionFailed(e.error, e.getMessage());
-                return;
-            }
-        }
-
-        if (sessionContainsApk(session)) {
-            try {
-                Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
-                        + session.sessionId + " by performing a dry-run install");
-                installApksInSession(session, /* preReboot */ true);
-                // TODO(b/118865310): abort the session on apexd.
-            } catch (PackageManagerException e) {
-                session.setStagedSessionFailed(e.error, e.getMessage());
-                return;
-            }
-        }
-
-        if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-            // If rollback is enabled for this session, we call through to the RollbackManager
-            // with the list of sessions it must enable rollback for. Note that notifyStagedSession
-            // is a synchronous operation.
-            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
-            try {
-                // NOTE: To stay consistent with the non-staged install flow, we don't fail the
-                // entire install if rollbacks can't be enabled.
-                if (!rm.notifyStagedSession(session.sessionId)) {
-                    Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
-                }
-            } catch (RemoteException re) {
-                // Cannot happen, the rollback manager is in the same process.
-            }
-        }
-
-        // Proactively mark session as ready before calling apexd. Although this call order looks
-        // counter-intuitive, this is the easiest way to ensure that session won't end up in the
-        // inconsistent state:
-        //  - If device gets rebooted right before call to apexd, then apexd will never activate
-        //      apex files of this staged session. This will result in StagingManager failing the
-        //      session.
-        // On the other hand, if the order of the calls was inverted (first call apexd, then mark
-        // session as ready), then if a device gets rebooted right after the call to apexd, only
-        // apex part of the train will be applied, leaving device in an inconsistent state.
-        Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
-        session.setStagedSessionReady();
-        if (!hasApex) {
-            // Session doesn't contain apex, nothing to do.
-            return;
-        }
-        try {
-            mApexManager.markStagedSessionReady(session.sessionId);
-        } catch (PackageManagerException e) {
-            session.setStagedSessionFailed(e.error, e.getMessage());
-        }
-    }
-
-
     private boolean sessionContains(@NonNull PackageInstallerSession session,
                                     Predicate<PackageInstallerSession> filter) {
         if (!session.isMultiPackage()) {
@@ -358,7 +301,7 @@
                 // Greedily re-trigger the pre-reboot verification.
                 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
                         + "verified, resuming pre-reboot verification");
-                mBgHandler.post(() -> preRebootVerification(session));
+                mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
                 return;
             }
             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
@@ -375,7 +318,7 @@
         // The APEX part of the session is activated, proceed with the installation of APKs.
         try {
             Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
-            installApksInSession(session, /* preReboot */ false);
+            installApksInSession(session);
         } catch (PackageManagerException e) {
             session.setStagedSessionFailed(e.error, e.getMessage());
 
@@ -467,53 +410,23 @@
         return apkSession;
     }
 
-    private void commitApkSession(@NonNull PackageInstallerSession apkSession,
-            int originalSessionId, boolean preReboot) throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
-        if (!preReboot) {
-            if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-                // If rollback is available for this session, notify the rollback
-                // manager of the apk session so it can properly enable rollback.
-                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
-                try {
-                    rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
-                } catch (RemoteException re) {
-                    // Cannot happen, the rollback manager is in the same process.
-                }
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        apkSession.commit(receiver.getIntentSender(), false);
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status != PackageInstaller.STATUS_SUCCESS) {
-
-            final String errorMessage = result.getStringExtra(
-                    PackageInstaller.EXTRA_STATUS_MESSAGE);
-            Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
-                    + errorMessage + "]");
-            throw new PackageManagerException(errorCode, errorMessage);
-        }
-    }
-
-    private void installApksInSession(@NonNull PackageInstallerSession session,
-                                         boolean preReboot) throws PackageManagerException {
+    /**
+     * Extract apks in the given session into a new session. Returns {@code null} if there is no
+     * apks in the given session. Only parent session is returned for multi-package session.
+     */
+    @Nullable
+    private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
+            boolean preReboot) throws PackageManagerException {
         final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
                 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
         if (!session.isMultiPackage() && !isApexSession(session)) {
-            // APK single-packaged staged session. Do a regular install.
-            PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot);
-            commitApkSession(apkSession, session.sessionId, preReboot);
+            return createAndWriteApkSession(session, preReboot);
         } else if (session.isMultiPackage()) {
             // For multi-package staged sessions containing APKs, we identify which child sessions
             // contain an APK, and with those then create a new multi-package group of sessions,
             // carrying over all the session parameters and unmarking them as staged. On commit the
             // sessions will be installed atomically.
-            List<PackageInstallerSession> childSessions;
+            final List<PackageInstallerSession> childSessions;
             synchronized (mStagedSessions) {
                 childSessions =
                         Arrays.stream(session.getChildSessionIds())
@@ -525,18 +438,18 @@
             }
             if (childSessions.isEmpty()) {
                 // APEX-only multi-package staged session, nothing to do.
-                return;
+                return null;
             }
-            PackageInstaller.SessionParams params = session.params.copy();
+            final PackageInstaller.SessionParams params = session.params.copy();
             params.isStaged = false;
             if (preReboot) {
                 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
             }
             // TODO(b/129744602): use the userid from the original session.
-            int apkParentSessionId = mPi.createSession(
+            final int apkParentSessionId = mPi.createSession(
                     params, session.getInstallerPackageName(),
                     0 /* UserHandle.SYSTEM */);
-            PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
+            final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
             try {
                 apkParentSession.open();
             } catch (IOException e) {
@@ -557,14 +470,80 @@
                             "Failed to add a child session " + apkChildSession.sessionId);
                 }
             }
-            commitApkSession(apkParentSession, session.sessionId, preReboot);
+            return apkParentSession;
         }
-        // APEX single-package staged session, nothing to do.
+        return null;
+    }
+
+    private void verifyApksInSession(PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToVerify = extractApksInSession(
+                session,  /* preReboot */ true);
+        if (apksToVerify == null) {
+            return;
+        }
+
+        final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
+                (Intent result) -> {
+                    int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                            PackageInstaller.STATUS_FAILURE);
+                    if (status != PackageInstaller.STATUS_SUCCESS) {
+                        final String errorMessage = result.getStringExtra(
+                                PackageInstaller.EXTRA_STATUS_MESSAGE);
+                        Slog.e(TAG, "Failure to verify APK staged session "
+                                + session.sessionId + " [" + errorMessage + "]");
+                        session.setStagedSessionFailed(
+                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
+                        return;
+                    }
+                    mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
+                            session.sessionId);
+                });
+
+        apksToVerify.commit(receiver.getIntentSender(), false);
+    }
+
+    private void installApksInSession(@NonNull PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToInstall = extractApksInSession(
+                session, /* preReboot */ false);
+        if (apksToInstall == null) {
+            return;
+        }
+
+        if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+            // If rollback is available for this session, notify the rollback
+            // manager of the apk session so it can properly enable rollback.
+            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+            try {
+                rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
+                        + session.sessionId, re);
+            }
+        }
+
+        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
+        apksToInstall.commit(receiver.getIntentSender(), false);
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status != PackageInstaller.STATUS_SUCCESS) {
+            final String errorMessage = result.getStringExtra(
+                    PackageInstaller.EXTRA_STATUS_MESSAGE);
+            Slog.e(TAG, "Failure to install APK staged session "
+                    + session.sessionId + " [" + errorMessage + "]");
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
+        }
     }
 
     void commitSession(@NonNull PackageInstallerSession session) {
         updateStoredSession(session);
-        mBgHandler.post(() -> preRebootVerification(session));
+        mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
     @Nullable
@@ -691,7 +670,7 @@
         if (!session.isStagedSessionReady()) {
             // The framework got restarted before the pre-reboot verification could complete,
             // restart the verification.
-            mBgHandler.post(() -> preRebootVerification(session));
+            mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
@@ -699,14 +678,34 @@
         }
     }
 
-    private static class LocalIntentReceiver {
+    private static class LocalIntentReceiverAsync {
+        final Consumer<Intent> mConsumer;
+
+        LocalIntentReceiverAsync(Consumer<Intent> consumer) {
+            mConsumer = consumer;
+        }
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                mConsumer.accept(intent);
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+    }
+
+    private static class LocalIntentReceiverSync {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                             IIntentReceiver finishedReceiver, String requiredPermission,
-                             Bundle options) {
+                    IIntentReceiver finishedReceiver, String requiredPermission,
+                    Bundle options) {
                 try {
                     mResult.offer(intent, 5, TimeUnit.SECONDS);
                 } catch (InterruptedException e) {
@@ -727,4 +726,189 @@
             }
         }
     }
+
+    private final class PreRebootVerificationHandler extends Handler {
+
+        PreRebootVerificationHandler(Looper looper) {
+            super(looper);
+        }
+
+        /**
+         * Handler for states of pre reboot verification. The states are arranged linearly (shown
+         * below) with each state either calling the next state, or calling some other method that
+         * eventually calls the next state.
+         *
+         * <p><ul>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
+         * </ul></p>
+         *
+         * Details about each of state can be found in corresponding handler of node.
+         */
+        private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
+
+        @Override
+        public void handleMessage(Message msg) {
+            final int sessionId = msg.arg1;
+            final PackageInstallerSession session;
+            synchronized (mStagedSessions) {
+                session = mStagedSessions.get(sessionId);
+            }
+            // Maybe session was aborted before pre-reboot verification was complete
+            if (session == null) {
+                Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
+                return;
+            }
+            switch (msg.what) {
+                case MSG_PRE_REBOOT_VERIFICATION_START:
+                    handlePreRebootVerification_Start(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_APEX:
+                    handlePreRebootVerification_Apex(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_APK:
+                    handlePreRebootVerification_Apk(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_END:
+                    handlePreRebootVerification_End(session);
+                    break;
+            }
+        }
+
+        // Method for starting the pre-reboot verification
+        private void startPreRebootVerification(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Start_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
+        }
+
+        /**
+         * A dummy state for starting the pre reboot verification.
+         *
+         * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
+         */
+        private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
+            Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
+            notifyPreRebootVerification_Start_Complete(session.sessionId);
+        }
+
+        /**
+         * Pre-reboot verification state for apex files:
+         *
+         * <p><ul>
+         *     <li>submits session to apex service</li>
+         *     <li>validates signatures of apex files</li>
+         * </ul></p>
+         */
+        private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
+            final boolean hasApex = sessionContainsApex(session);
+
+            // APEX checks. For single-package sessions, check if they contain an APEX. For
+            // multi-package sessions, find all the child sessions that contain an APEX.
+            if (hasApex) {
+                try {
+                    final List<PackageInfo> apexPackages =
+                            submitSessionToApexService(session);
+                    for (PackageInfo apexPackage : apexPackages) {
+                        validateApexSignature(
+                                apexPackage, session.params.installFlags);
+                    }
+                } catch (PackageManagerException e) {
+                    session.setStagedSessionFailed(e.error, e.getMessage());
+                    return;
+                }
+            }
+
+            notifyPreRebootVerification_Apex_Complete(session.sessionId);
+        }
+
+        /**
+         * Pre-reboot verification state for apk files:
+         *   <p><ul>
+         *       <li>performs a dry-run install of apk</li>
+         *   </ul></p>
+         */
+        private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
+            if (!sessionContainsApk(session)) {
+                notifyPreRebootVerification_Apk_Complete(session.sessionId);
+                return;
+            }
+
+            try {
+                Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
+                        + session.sessionId + " by performing a dry-run install");
+
+                // verifyApksInSession will notify the handler when APK verification is complete
+                verifyApksInSession(session);
+                // TODO(b/118865310): abort the session on apexd.
+            } catch (PackageManagerException e) {
+                session.setStagedSessionFailed(e.error, e.getMessage());
+            }
+        }
+
+        /**
+         * Pre-reboot verification state for wrapping up:
+         * <p><ul>
+         *     <li>enables rollback if required</li>
+         *     <li>marks session as ready</li>
+         * </ul></p>
+         */
+        private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
+            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+                // If rollback is enabled for this session, we call through to the RollbackManager
+                // with the list of sessions it must enable rollback for. Note that
+                // notifyStagedSession is a synchronous operation.
+                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
+                try {
+                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
+                    // entire install if rollbacks can't be enabled.
+                    if (!rm.notifyStagedSession(session.sessionId)) {
+                        Slog.e(TAG, "Unable to enable rollback for session: "
+                                + session.sessionId);
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                            + session.sessionId, re);
+                }
+            }
+
+            // Proactively mark session as ready before calling apexd. Although this call order
+            // looks counter-intuitive, this is the easiest way to ensure that session won't end up
+            // in the inconsistent state:
+            //  - If device gets rebooted right before call to apexd, then apexd will never activate
+            //      apex files of this staged session. This will result in StagingManager failing
+            //      the session.
+            // On the other hand, if the order of the calls was inverted (first call apexd, then
+            // mark session as ready), then if a device gets rebooted right after the call to apexd,
+            // only apex part of the train will be applied, leaving device in an inconsistent state.
+            Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
+            session.setStagedSessionReady();
+            final boolean hasApex = sessionContainsApex(session);
+            if (!hasApex) {
+                // Session doesn't contain apex, nothing to do.
+                return;
+            }
+            try {
+                mApexManager.markStagedSessionReady(session.sessionId);
+            } catch (PackageManagerException e) {
+                session.setStagedSessionFailed(e.error, e.getMessage());
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1fe5512..9371c44 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -404,10 +404,10 @@
                 return;
             }
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-            final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
             // Call setQuietModeEnabled on bg thread to avoid ANR
             BackgroundThread.getHandler().post(() ->
-                    setQuietModeEnabled(userHandle, false, target, /* callingPackage */ null));
+                    setQuietModeEnabled(userId, false, target, /* callingPackage */ null));
         }
     };
 
@@ -482,9 +482,9 @@
         }
 
         @Override
-        public void onStartUser(int userHandle) {
+        public void onStartUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.startRealtime = SystemClock.elapsedRealtime();
                 }
@@ -492,9 +492,9 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
+        public void onUnlockUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.unlockRealtime = SystemClock.elapsedRealtime();
                 }
@@ -502,9 +502,9 @@
         }
 
         @Override
-        public void onStopUser(int userHandle) {
+        public void onStopUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.startRealtime = 0;
                     user.unlockRealtime = 0;
@@ -610,7 +610,7 @@
     }
 
     @Override
-    public String getUserAccount(int userId) {
+    public String getUserAccount(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("get user account");
         synchronized (mUsersLock) {
             return mUsers.get(userId).account;
@@ -618,7 +618,7 @@
     }
 
     @Override
-    public void setUserAccount(int userId, String accountName) {
+    public void setUserAccount(@UserIdInt int userId, String accountName) {
         checkManageUserAndAcrossUsersFullPermission("set user account");
         UserData userToUpdate = null;
         synchronized (mPackagesLock) {
@@ -676,7 +676,7 @@
     }
 
     @Override
-    public List<UserInfo> getProfiles(int userId, boolean enabledOnly) {
+    public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) {
         boolean returnFullInfo = true;
         if (userId != UserHandle.getCallingUserId()) {
             checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
@@ -694,7 +694,7 @@
     }
 
     @Override
-    public int[] getProfileIds(int userId, boolean enabledOnly) {
+    public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
         if (userId != UserHandle.getCallingUserId()) {
             checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
         }
@@ -710,7 +710,8 @@
 
     /** Assume permissions already checked and caller's identity cleared */
     @GuardedBy("mUsersLock")
-    private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly, boolean fullInfo) {
+    private List<UserInfo> getProfilesLU(@UserIdInt int userId, boolean enabledOnly,
+            boolean fullInfo) {
         IntArray profileIds = getProfileIdsLU(userId, enabledOnly);
         ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
         for (int i = 0; i < profileIds.size(); i++) {
@@ -733,7 +734,7 @@
      *  Assume permissions already checked and caller's identity cleared
      */
     @GuardedBy("mUsersLock")
-    private IntArray getProfileIdsLU(int userId, boolean enabledOnly) {
+    private IntArray getProfileIdsLU(@UserIdInt int userId, boolean enabledOnly) {
         UserInfo user = getUserInfoLU(userId);
         IntArray result = new IntArray(mUsers.size());
         if (user == null) {
@@ -761,28 +762,28 @@
     }
 
     @Override
-    public int getCredentialOwnerProfile(int userHandle) {
+    public int getCredentialOwnerProfile(@UserIdInt int userId) {
         checkManageUsersPermission("get the credential owner");
-        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
+        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
             synchronized (mUsersLock) {
-                UserInfo profileParent = getProfileParentLU(userHandle);
+                UserInfo profileParent = getProfileParentLU(userId);
                 if (profileParent != null) {
                     return profileParent.id;
                 }
             }
         }
 
-        return userHandle;
+        return userId;
     }
 
     @Override
-    public boolean isSameProfileGroup(int userId, int otherUserId) {
+    public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
         if (userId == otherUserId) return true;
         checkManageUsersPermission("check if in the same profile group");
         return isSameProfileGroupNoChecks(userId, otherUserId);
     }
 
-    private boolean isSameProfileGroupNoChecks(int userId, int otherUserId) {
+    private boolean isSameProfileGroupNoChecks(@UserIdInt int userId, int otherUserId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
@@ -798,27 +799,27 @@
     }
 
     @Override
-    public UserInfo getProfileParent(int userHandle) {
+    public UserInfo getProfileParent(@UserIdInt int userId) {
         checkManageUsersPermission("get the profile parent");
         synchronized (mUsersLock) {
-            return getProfileParentLU(userHandle);
+            return getProfileParentLU(userId);
         }
     }
 
     @Override
-    public int getProfileParentId(int userHandle) {
+    public int getProfileParentId(@UserIdInt int userId) {
         checkManageUsersPermission("get the profile parent");
-        return mLocalService.getProfileParentId(userHandle);
+        return mLocalService.getProfileParentId(userId);
     }
 
     @GuardedBy("mUsersLock")
-    private UserInfo getProfileParentLU(int userHandle) {
-        UserInfo profile = getUserInfoLU(userHandle);
+    private UserInfo getProfileParentLU(@UserIdInt int userId) {
+        UserInfo profile = getUserInfoLU(userId);
         if (profile == null) {
             return null;
         }
         int parentUserId = profile.profileGroupId;
-        if (parentUserId == userHandle || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+        if (parentUserId == userId || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
             return null;
         } else {
             return getUserInfoLU(parentUserId);
@@ -848,7 +849,7 @@
 
     @Override
     public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
-            int userHandle, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target) {
         Preconditions.checkNotNull(callingPackage);
 
         if (enableQuietMode && target != null) {
@@ -862,17 +863,17 @@
             boolean result = false;
             if (enableQuietMode) {
                 setQuietModeEnabled(
-                        userHandle, true /* enableQuietMode */, target, callingPackage);
+                        userId, true /* enableQuietMode */, target, callingPackage);
                 result = true;
             } else {
                 boolean needToShowConfirmCredential =
-                        mLockPatternUtils.isSecure(userHandle)
-                                && !StorageManager.isUserKeyUnlocked(userHandle);
+                        mLockPatternUtils.isSecure(userId)
+                                && !StorageManager.isUserKeyUnlocked(userId);
                 if (needToShowConfirmCredential) {
-                    showConfirmCredentialToDisableQuietMode(userHandle, target);
+                    showConfirmCredentialToDisableQuietMode(userId, target);
                 } else {
                     setQuietModeEnabled(
-                            userHandle, false /* enableQuietMode */, target, callingPackage);
+                            userId, false /* enableQuietMode */, target, callingPackage);
                     result = true;
                 }
             }
@@ -922,16 +923,16 @@
                 + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission");
     }
 
-    private void setQuietModeEnabled(int userHandle, boolean enableQuietMode,
+    private void setQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             IntentSender target, @Nullable String callingPackage) {
         final UserInfo profile, parent;
         final UserData profileUserData;
         synchronized (mUsersLock) {
-            profile = getUserInfoLU(userHandle);
-            parent = getProfileParentLU(userHandle);
+            profile = getUserInfoLU(userId);
+            parent = getProfileParentLU(userId);
 
             if (profile == null || !profile.isManagedProfile()) {
-                throw new IllegalArgumentException("User " + userHandle + " is not a profile");
+                throw new IllegalArgumentException("User " + userId + " is not a profile");
             }
             if (profile.isQuietModeEnabled() == enableQuietMode) {
                 Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
@@ -945,17 +946,17 @@
         }
         try {
             if (enableQuietMode) {
-                ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+                ActivityManager.getService().stopUser(userId, /* force */true, null);
                 LocalServices.getService(ActivityManagerInternal.class)
-                        .killForegroundAppsForUser(userHandle);
+                        .killForegroundAppsForUser(userId);
             } else {
                 IProgressListener callback = target != null
                         ? new DisableQuietModeUserUnlockedCallback(target)
                         : null;
                 ActivityManager.getService().startUserInBackgroundWithListener(
-                        userHandle, callback);
+                        userId, callback);
             }
-            logQuietModeEnabled(userHandle, enableQuietMode, callingPackage);
+            logQuietModeEnabled(userId, enableQuietMode, callingPackage);
         } catch (RemoteException e) {
             // Should not happen, same process.
             e.rethrowAsRuntimeException();
@@ -964,11 +965,11 @@
                 enableQuietMode);
     }
 
-    private void logQuietModeEnabled(int userHandle, boolean enableQuietMode,
+    private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             @Nullable String callingPackage) {
         UserData userData;
         synchronized (mUsersLock) {
-            userData = getUserDataLU(userHandle);
+            userData = getUserDataLU(userId);
         }
         if (userData == null) {
             return;
@@ -987,11 +988,11 @@
     }
 
     @Override
-    public boolean isQuietModeEnabled(int userHandle) {
+    public boolean isQuietModeEnabled(@UserIdInt int userId) {
         synchronized (mPackagesLock) {
             UserInfo info;
             synchronized (mUsersLock) {
-                info = getUserInfoLU(userHandle);
+                info = getUserInfoLU(userId);
             }
             if (info == null || !info.isManagedProfile()) {
                 return false;
@@ -1004,15 +1005,14 @@
      * Show confirm credential screen to unlock user in order to turn off quiet mode.
      */
     private void showConfirmCredentialToDisableQuietMode(
-            @UserIdInt int userHandle, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target) {
         // otherwise, we show a profile challenge to trigger decryption of the user
         final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
                 Context.KEYGUARD_SERVICE);
-        // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+        // We should use userId not credentialOwnerUserId here, as even if it is unified
         // lock, confirm screenlock page will know and show personal challenge, and unlock
         // work profile when personal challenge is correct
-        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
-                userHandle);
+        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
         if (unlockIntent == null) {
             return;
         }
@@ -1021,7 +1021,7 @@
         if (target != null) {
             callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
         callBackIntent.setPackage(mContext.getPackageName());
         callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
@@ -1039,7 +1039,7 @@
     }
 
     @Override
-    public void setUserEnabled(int userId) {
+    public void setUserEnabled(@UserIdInt int userId) {
         checkManageUsersPermission("enable user");
         synchronized (mPackagesLock) {
             UserInfo info;
@@ -1054,7 +1054,7 @@
     }
 
     @Override
-    public void setUserAdmin(int userId) {
+    public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
 
         synchronized (mPackagesLock) {
@@ -1097,7 +1097,7 @@
     }
 
     @Override
-    public UserInfo getUserInfo(int userId) {
+    public UserInfo getUserInfo(@UserIdInt int userId) {
         checkManageOrCreateUsersPermission("query user");
         synchronized (mUsersLock) {
             return userWithName(getUserInfoLU(userId));
@@ -1128,7 +1128,7 @@
     }
 
     @Override
-    public boolean isManagedProfile(int userId) {
+    public boolean isManagedProfile(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isManagedProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
@@ -1137,19 +1137,19 @@
     }
 
     @Override
-    public boolean isUserUnlockingOrUnlocked(int userId) {
+    public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked");
         return mLocalService.isUserUnlockingOrUnlocked(userId);
     }
 
     @Override
-    public boolean isUserUnlocked(int userId) {
+    public boolean isUserUnlocked(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
         return mLocalService.isUserUnlocked(userId);
     }
 
     @Override
-    public boolean isUserRunning(int userId) {
+    public boolean isUserRunning(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning");
         return mLocalService.isUserRunning(userId);
     }
@@ -1190,7 +1190,8 @@
         }
     }
 
-    private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
+    private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId,
+            String name) {
         int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
                 hasManageUsersPermission()) {
@@ -1204,7 +1205,7 @@
     }
 
     @Override
-    public boolean isDemoUser(int userId) {
+    public boolean isDemoUser(@UserIdInt int userId) {
         int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId != userId && !hasManageUsersPermission()) {
             throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
@@ -1224,7 +1225,7 @@
     }
 
     @Override
-    public boolean canHaveRestrictedProfile(int userId) {
+    public boolean canHaveRestrictedProfile(@UserIdInt int userId) {
         checkManageUsersPermission("canHaveRestrictedProfile");
         synchronized (mUsersLock) {
             final UserInfo userInfo = getUserInfoLU(userId);
@@ -1260,7 +1261,7 @@
      * Should be locked on mUsers before calling this.
      */
     @GuardedBy("mUsersLock")
-    private UserInfo getUserInfoLU(int userId) {
+    private UserInfo getUserInfoLU(@UserIdInt int userId) {
         final UserData userData = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
         if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
@@ -1271,7 +1272,7 @@
     }
 
     @GuardedBy("mUsersLock")
-    private UserData getUserDataLU(int userId) {
+    private UserData getUserDataLU(@UserIdInt int userId) {
         final UserData userData = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
         if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
@@ -1284,7 +1285,7 @@
      * Obtains {@link #mUsersLock} and return UserInfo from mUsers.
      * <p>No permissions checking or any addition checks are made</p>
      */
-    private UserInfo getUserInfoNoChecks(int userId) {
+    private UserInfo getUserInfoNoChecks(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             final UserData userData = mUsers.get(userId);
             return userData != null ? userData.info : null;
@@ -1295,19 +1296,19 @@
      * Obtains {@link #mUsersLock} and return UserData from mUsers.
      * <p>No permissions checking or any addition checks are made</p>
      */
-    private UserData getUserDataNoChecks(int userId) {
+    private UserData getUserDataNoChecks(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             return mUsers.get(userId);
         }
     }
 
     /** Called by PackageManagerService */
-    public boolean exists(int userId) {
+    public boolean exists(@UserIdInt int userId) {
         return mLocalService.exists(userId);
     }
 
     @Override
-    public void setUserName(int userId, String name) {
+    public void setUserName(@UserIdInt int userId, String name) {
         checkManageUsersPermission("rename users");
         boolean changed = false;
         synchronized (mPackagesLock) {
@@ -1333,7 +1334,7 @@
     }
 
     @Override
-    public void setUserIcon(int userId, Bitmap bitmap) {
+    public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
         checkManageUsersPermission("update users");
         if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
             Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
@@ -1344,7 +1345,7 @@
 
 
 
-    private void sendUserInfoChangedBroadcast(int userId) {
+    private void sendUserInfoChangedBroadcast(@UserIdInt int userId) {
         Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
         changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1389,7 +1390,7 @@
         return null;
     }
 
-    public void makeInitialized(int userId) {
+    public void makeInitialized(@UserIdInt int userId) {
         checkManageUsersPermission("makeInitialized");
         boolean scheduleWriteUser = false;
         UserData userData;
@@ -1447,8 +1448,8 @@
     /**
      * See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
      */
-    private void setDevicePolicyUserRestrictionsInner(int userId, @Nullable Bundle restrictions,
-            boolean isDeviceOwner, int cameraRestrictionScope) {
+    private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
+            @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
         final Bundle global = new Bundle();
         final Bundle local = new Bundle();
 
@@ -1505,8 +1506,8 @@
      * empty, record is removed from the array.
      * @return whether restrictions bundle is different from the old one.
      */
-    private boolean updateRestrictionsIfNeededLR(int userId, @Nullable Bundle restrictions,
-            SparseArray<Bundle> restrictionsArray) {
+    private boolean updateRestrictionsIfNeededLR(@UserIdInt int userId,
+            @Nullable Bundle restrictions, SparseArray<Bundle> restrictionsArray) {
         final boolean changed =
                 !UserRestrictionsUtils.areEqual(restrictionsArray.get(userId), restrictions);
         if (changed) {
@@ -1520,7 +1521,7 @@
     }
 
     @GuardedBy("mRestrictionsLock")
-    private Bundle computeEffectiveUserRestrictionsLR(int userId) {
+    private Bundle computeEffectiveUserRestrictionsLR(@UserIdInt int userId) {
         final Bundle baseRestrictions =
                 UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
         final Bundle global = UserRestrictionsUtils.mergeAll(mDevicePolicyGlobalUserRestrictions);
@@ -1538,14 +1539,14 @@
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void invalidateEffectiveUserRestrictionsLR(int userId) {
+    private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
         if (DBG) {
             Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
         }
         mCachedEffectiveUserRestrictions.remove(userId);
     }
 
-    private Bundle getEffectiveUserRestrictions(int userId) {
+    private Bundle getEffectiveUserRestrictions(@UserIdInt int userId) {
         synchronized (mRestrictionsLock) {
             Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
             if (restrictions == null) {
@@ -1558,7 +1559,7 @@
 
     /** @return a specific user restriction that's in effect currently. */
     @Override
-    public boolean hasUserRestriction(String restrictionKey, int userId) {
+    public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
         return mLocalService.hasUserRestriction(restrictionKey, userId);
     }
 
@@ -1593,7 +1594,7 @@
      *         and {@link UserManager#RESTRICTION_SOURCE_PROFILE_OWNER}
      */
     @Override
-    public int getUserRestrictionSource(String restrictionKey, int userId) {
+    public int getUserRestrictionSource(String restrictionKey, @UserIdInt int userId) {
         List<EnforcingUser> enforcingUsers = getUserRestrictionSources(restrictionKey,  userId);
         // Get "bitwise or" of restriction sources for all enforcing users.
         int result = UserManager.RESTRICTION_NOT_SET;
@@ -1652,12 +1653,12 @@
      * {@link Bundle}.
      */
     @Override
-    public Bundle getUserRestrictions(int userId) {
+    public Bundle getUserRestrictions(@UserIdInt int userId) {
         return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
-    public boolean hasBaseUserRestriction(String restrictionKey, int userId) {
+    public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) {
         checkManageUsersPermission("hasBaseUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
             return false;
@@ -1669,7 +1670,7 @@
     }
 
     @Override
-    public void setUserRestriction(String key, boolean value, int userId) {
+    public void setUserRestriction(String key, boolean value, @UserIdInt int userId) {
         checkManageUsersPermission("setUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(key)) {
             return;
@@ -1695,7 +1696,7 @@
      */
     @GuardedBy("mRestrictionsLock")
     private void updateUserRestrictionsInternalLR(
-            @Nullable Bundle newBaseRestrictions, int userId) {
+            @Nullable Bundle newBaseRestrictions, @UserIdInt int userId) {
         final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
                 mAppliedUserRestrictions.get(userId));
 
@@ -1779,7 +1780,7 @@
 
     // Package private for the inner class.
     @GuardedBy("mRestrictionsLock")
-    void applyUserRestrictionsLR(int userId) {
+    void applyUserRestrictionsLR(@UserIdInt int userId) {
         updateUserRestrictionsInternalLR(null, userId);
     }
 
@@ -1831,7 +1832,7 @@
     }
 
     @Override
-    public boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne) {
+    public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
         checkManageUsersPermission("check if more managed profiles can be added.");
         if (ActivityManager.isLowRamDeviceStatic()) {
             return false;
@@ -2252,10 +2253,11 @@
 
     @GuardedBy({"mPackagesLock", "mRestrictionsLock"})
     private void fallbackToSingleUserLP() {
-        int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN;
-        // In headless system user mode, the primary flag is assigned to the first human user.
+        int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
+                | UserInfo.FLAG_PRIMARY;
+        // In headless system user mode, headless system user is not a full user.
         if (!UserManager.isHeadlessSystemUserMode()) {
-            flags |= UserInfo.FLAG_PRIMARY | UserInfo.FLAG_FULL;
+            flags |= UserInfo.FLAG_FULL;
         }
         // Create the system user
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags);
@@ -2661,7 +2663,7 @@
     /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
      */
-    private static void cleanAppRestrictionsForPackageLAr(String pkg, int userId) {
+    private static void cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
         File dir = Environment.getUserSystemDirectory(userId);
         File resFile = new File(dir, packageToRestrictionsFileName(pkg));
         if (resFile.exists()) {
@@ -2670,23 +2672,23 @@
     }
 
     @Override
-    public UserInfo createProfileForUser(String name, int flags, int userId,
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId,
             String[] disallowedPackages) {
         checkManageOrCreateUsersPermission(flags);
         return createUserInternal(name, flags, userId, disallowedPackages);
     }
 
     @Override
-    public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, int userId,
-            String[] disallowedPackages) {
+    public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
+            @UserIdInt int userId, String[] disallowedPackages) {
         checkManageOrCreateUsersPermission(flags);
         return createUserInternalUnchecked(name, flags, userId, disallowedPackages);
     }
 
     @Override
-    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         checkManageOrCreateUsersPermission("Only the system can remove users");
-        return removeUserUnchecked(userHandle);
+        return removeUserUnchecked(userId);
     }
 
     @Override
@@ -2711,14 +2713,25 @@
         return createUserInternalUnchecked(name, flags, parentId, disallowedPackages);
     }
 
-    private UserInfo createUserInternalUnchecked(String name, int flags, int parentId,
-            String[] disallowedPackages) {
+    private UserInfo createUserInternalUnchecked(@Nullable String name, int flags,
+            int parentId, @Nullable String[] disallowedPackages) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+        t.traceBegin("createUser");
+        UserInfo userInfo =
+                createUserInternalUncheckedNoTracing(name, flags, parentId, disallowedPackages, t);
+        t.traceEnd();
+        return userInfo;
+    }
+
+    private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, int flags,
+            int parentId, @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) {
         DeviceStorageMonitorInternal dsm = LocalServices
                 .getService(DeviceStorageMonitorInternal.class);
         if (dsm.isMemoryLow()) {
             Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
             return null;
         }
+
         final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
@@ -2769,17 +2782,7 @@
                         return null;
                     }
                 }
-                // In headless system user mode, we assign the first human user the primary flag.
-                // And if there is no device owner, we also assign the admin flag to primary user.
-                if (UserManager.isHeadlessSystemUserMode()
-                        && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
-                    flags |= UserInfo.FLAG_PRIMARY;
-                    synchronized (mUsersLock) {
-                        if (!mIsDeviceManaged) {
-                            flags |= UserInfo.FLAG_ADMIN;
-                        }
-                    }
-                }
+
                 if (!isManagedProfile) {
                     // New users cannot be system, and it's not a profile, so per-force it's FULL.
                     flags |= UserInfo.FLAG_FULL;
@@ -2828,11 +2831,21 @@
                     }
                 }
             }
+
+            t.traceBegin("createUserKey");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
             storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            t.traceEnd();
+
+            t.traceBegin("prepareUserData");
             mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+            t.traceEnd();
+
+            t.traceBegin("PM.createNewUser");
             mPm.createNewUser(userId, disallowedPackages);
+            t.traceEnd();
+
             userInfo.partial = false;
             synchronized (mPackagesLock) {
                 writeUserLP(userData);
@@ -2847,7 +2860,11 @@
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.append(userId, restrictions);
             }
+
+            t.traceBegin("PM.onNewUserCreated");
             mPm.onNewUserCreated(userId);
+            t.traceEnd();
+
             Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
             addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
@@ -2871,7 +2888,7 @@
     }
 
     @VisibleForTesting
-    void removeUserInfo(int userId) {
+    void removeUserInfo(@UserIdInt int userId) {
         synchronized (mUsers) {
             mUsers.remove(userId);
         }
@@ -2923,11 +2940,11 @@
     /**
      * Mark this guest user for deletion to allow us to create another guest
      * and switch to that user before actually removing this guest.
-     * @param userHandle the userid of the current guest
+     * @param userId the userid of the current guest
      * @return whether the user could be marked for deletion
      */
     @Override
-    public boolean markGuestForDeletion(int userHandle) {
+    public boolean markGuestForDeletion(@UserIdInt int userId) {
         checkManageUsersPermission("Only the system can remove users");
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
                 UserManager.DISALLOW_REMOVE_USER, false)) {
@@ -2940,8 +2957,8 @@
             final UserData userData;
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    userData = mUsers.get(userHandle);
-                    if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
+                    userData = mUsers.get(userId);
+                    if (userId == 0 || userData == null || mRemovingUserIds.get(userId)) {
                         return false;
                     }
                 }
@@ -2968,16 +2985,16 @@
     /**
      * Removes a user and all data directories created for that user. This method should be called
      * after the user's processes have been terminated.
-     * @param userHandle the user's id
+     * @param userId the user's id
      */
     @Override
-    public boolean removeUser(int userHandle) {
-        Slog.i(LOG_TAG, "removeUser u" + userHandle);
+    public boolean removeUser(@UserIdInt int userId) {
+        Slog.i(LOG_TAG, "removeUser u" + userId);
         checkManageOrCreateUsersPermission("Only the system can remove users");
 
         final boolean isManagedProfile;
         synchronized (mUsersLock) {
-            UserInfo userInfo = getUserInfoLU(userHandle);
+            UserInfo userInfo = getUserInfoLU(userId);
             isManagedProfile = userInfo != null && userInfo.isManagedProfile();
         }
         String restriction = isManagedProfile
@@ -2986,39 +3003,39 @@
             Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
             return false;
         }
-        return removeUserUnchecked(userHandle);
+        return removeUserUnchecked(userId);
     }
 
-    private boolean removeUserUnchecked(int userHandle) {
+    private boolean removeUserUnchecked(@UserIdInt int userId) {
         long ident = Binder.clearCallingIdentity();
         try {
             final UserData userData;
             int currentUser = ActivityManager.getCurrentUser();
-            if (currentUser == userHandle) {
+            if (currentUser == userId) {
                 Log.w(LOG_TAG, "Current user cannot be removed.");
                 return false;
             }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    userData = mUsers.get(userHandle);
-                    if (userHandle == UserHandle.USER_SYSTEM) {
+                    userData = mUsers.get(userId);
+                    if (userId == UserHandle.USER_SYSTEM) {
                         Log.e(LOG_TAG, "System user cannot be removed.");
                         return false;
                     }
 
                     if (userData == null) {
                         Log.e(LOG_TAG, String.format(
-                                "Cannot remove user %d, invalid user id provided.", userHandle));
+                                "Cannot remove user %d, invalid user id provided.", userId));
                         return false;
                     }
 
-                    if (mRemovingUserIds.get(userHandle)) {
+                    if (mRemovingUserIds.get(userId)) {
                         Log.e(LOG_TAG, String.format(
-                                "User %d is already scheduled for removal.", userHandle));
+                                "User %d is already scheduled for removal.", userId));
                         return false;
                     }
 
-                    addRemovingUserIdLocked(userHandle);
+                    addRemovingUserIdLocked(userId);
                 }
 
                 // Set this to a partially created user, so that the user will be purged
@@ -3031,7 +3048,7 @@
                 writeUserLP(userData);
             }
             try {
-                mAppOpsService.removeUser(userHandle);
+                mAppOpsService.removeUser(userId);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
@@ -3043,17 +3060,17 @@
                 sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id);
             }
 
-            if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
+            if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
             int res;
             try {
-                res = ActivityManager.getService().stopUser(userHandle, /* force= */ true,
+                res = ActivityManager.getService().stopUser(userId, /* force= */ true,
                 new IStopUserCallback.Stub() {
                             @Override
-                            public void userStopped(int userId) {
-                                finishRemoveUser(userId);
+                            public void userStopped(int userIdParam) {
+                                finishRemoveUser(userIdParam);
                             }
                             @Override
-                            public void userStopAborted(int userId) {
+                            public void userStopAborted(int userIdParam) {
                             }
                         });
             } catch (RemoteException e) {
@@ -3068,7 +3085,7 @@
 
     @GuardedBy("mUsersLock")
     @VisibleForTesting
-    void addRemovingUserIdLocked(int userId) {
+    void addRemovingUserIdLocked(@UserIdInt int userId) {
         // We remember deleted user IDs to prevent them from being
         // reused during the current boot; they can still be reused
         // after a reboot or recycling of userIds.
@@ -3080,14 +3097,14 @@
         }
     }
 
-    void finishRemoveUser(final int userHandle) {
-        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
+    void finishRemoveUser(final @UserIdInt int userId) {
+        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userId);
         // Let other services shutdown any activity and clean up their state before completely
         // wiping the user's system directory and removing from the user list
         long ident = Binder.clearCallingIdentity();
         try {
             Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
-            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
                     android.Manifest.permission.MANAGE_USERS,
 
@@ -3097,15 +3114,15 @@
                             if (DBG) {
                                 Slog.i(LOG_TAG,
                                         "USER_REMOVED broadcast sent, cleaning up user data "
-                                        + userHandle);
+                                        + userId);
                             }
                             new Thread() {
                                 @Override
                                 public void run() {
                                     // Clean up any ActivityTaskManager state
                                     LocalServices.getService(ActivityTaskManagerInternal.class)
-                                            .onUserStopped(userHandle);
-                                    removeUserState(userHandle);
+                                            .onUserStopped(userId);
+                                    removeUserState(userId);
                                 }
                             }.start();
                         }
@@ -3117,47 +3134,46 @@
         }
     }
 
-    private void removeUserState(final int userHandle) {
+    private void removeUserState(final @UserIdInt int userId) {
         try {
-            mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
+            mContext.getSystemService(StorageManager.class).destroyUserKey(userId);
         } catch (IllegalStateException e) {
             // This may be simply because the user was partially created.
-            Slog.i(LOG_TAG,
-                "Destroying key for user " + userHandle + " failed, continuing anyway", e);
+            Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
         }
 
         // Cleanup gatekeeper secure user id
         try {
             final IGateKeeperService gk = GateKeeper.getService();
             if (gk != null) {
-                gk.clearSecureUserId(userHandle);
+                gk.clearSecureUserId(userId);
             }
         } catch (Exception ex) {
             Slog.w(LOG_TAG, "unable to clear GK secure user id");
         }
 
         // Cleanup package manager settings
-        mPm.cleanUpUser(this, userHandle);
+        mPm.cleanUpUser(this, userId);
 
         // Clean up all data before removing metadata
-        mUserDataPreparer.destroyUserData(userHandle,
+        mUserDataPreparer.destroyUserData(userId,
                 StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
 
         // Remove this user from the list
         synchronized (mUsersLock) {
-            mUsers.remove(userHandle);
-            mIsUserManaged.delete(userHandle);
+            mUsers.remove(userId);
+            mIsUserManaged.delete(userId);
         }
         synchronized (mUserStates) {
-            mUserStates.delete(userHandle);
+            mUserStates.delete(userId);
         }
         synchronized (mRestrictionsLock) {
-            mBaseUserRestrictions.remove(userHandle);
-            mAppliedUserRestrictions.remove(userHandle);
-            mCachedEffectiveUserRestrictions.remove(userHandle);
-            mDevicePolicyLocalUserRestrictions.remove(userHandle);
-            if (mDevicePolicyGlobalUserRestrictions.get(userHandle) != null) {
-                mDevicePolicyGlobalUserRestrictions.remove(userHandle);
+            mBaseUserRestrictions.remove(userId);
+            mAppliedUserRestrictions.remove(userId);
+            mCachedEffectiveUserRestrictions.remove(userId);
+            mDevicePolicyLocalUserRestrictions.remove(userId);
+            if (mDevicePolicyGlobalUserRestrictions.get(userId) != null) {
+                mDevicePolicyGlobalUserRestrictions.remove(userId);
                 applyUserRestrictionsForAllUsersLR();
             }
         }
@@ -3166,12 +3182,12 @@
             writeUserListLP();
         }
         // Remove user file
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userId + XML_SUFFIX));
         userFile.delete();
         updateUserIds();
         if (RELEASE_DELETED_USER_ID) {
             synchronized (mUsers) {
-                mRemovingUserIds.delete(userHandle);
+                mRemovingUserIds.delete(userId);
             }
         }
     }
@@ -3191,7 +3207,7 @@
     }
 
     @Override
-    public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
+    public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
             checkSystemOrRoot("get application restrictions for other user/app " + packageName);
@@ -3204,7 +3220,7 @@
 
     @Override
     public void setApplicationRestrictions(String packageName, Bundle restrictions,
-            int userId) {
+            @UserIdInt int userId) {
         checkSystemOrRoot("set application restrictions");
         if (restrictions != null) {
             restrictions.setDefusable(true);
@@ -3238,7 +3254,8 @@
     }
 
     @GuardedBy("mAppRestrictionsLock")
-    private static Bundle readApplicationRestrictionsLAr(String packageName, int userId) {
+    private static Bundle readApplicationRestrictionsLAr(String packageName,
+            @UserIdInt int userId) {
         AtomicFile restrictionsFile =
                 new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
@@ -3332,7 +3349,7 @@
 
     @GuardedBy("mAppRestrictionsLock")
     private static void writeApplicationRestrictionsLAr(String packageName,
-            Bundle restrictions, int userId) {
+            Bundle restrictions, @UserIdInt int userId) {
         AtomicFile restrictionsFile = new AtomicFile(
                 new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
@@ -3410,17 +3427,17 @@
     }
 
     @Override
-    public int getUserSerialNumber(int userHandle) {
+    public int getUserSerialNumber(@UserIdInt int userId) {
         synchronized (mUsersLock) {
-            final UserInfo userInfo = getUserInfoLU(userHandle);
+            final UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null ? userInfo.serialNumber : -1;
         }
     }
 
     @Override
-    public boolean isUserNameSet(int userHandle) {
+    public boolean isUserNameSet(@UserIdInt int userId) {
         synchronized (mUsersLock) {
-            final UserInfo userInfo = getUserInfoLU(userHandle);
+            final UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.name != null;
         }
     }
@@ -3438,21 +3455,21 @@
     }
 
     @Override
-    public long getUserCreationTime(int userHandle) {
+    public long getUserCreationTime(@UserIdInt int userId) {
         int callingUserId = UserHandle.getCallingUserId();
         UserInfo userInfo = null;
         synchronized (mUsersLock) {
-            if (callingUserId == userHandle) {
-                userInfo = getUserInfoLU(userHandle);
+            if (callingUserId == userId) {
+                userInfo = getUserInfoLU(userId);
             } else {
-                UserInfo parent = getProfileParentLU(userHandle);
+                UserInfo parent = getProfileParentLU(userId);
                 if (parent != null && parent.id == callingUserId) {
-                    userInfo = getUserInfoLU(userHandle);
+                    userInfo = getUserInfoLU(userId);
                 }
             }
         }
         if (userInfo == null) {
-            throw new SecurityException("userHandle can only be the calling user or a managed "
+            throw new SecurityException("userId can only be the calling user or a managed "
                     + "profile associated with this user");
         }
         return userInfo.creationTime;
@@ -3485,7 +3502,7 @@
      * Called right before a user is started. This gives us a chance to prepare
      * app storage and apply any user restrictions.
      */
-    public void onBeforeStartUser(int userId) {
+    public void onBeforeStartUser(@UserIdInt int userId) {
         UserInfo userInfo = getUserInfo(userId);
         if (userInfo == null) {
             return;
@@ -3600,7 +3617,7 @@
     }
 
     @Override
-    public void setSeedAccountData(int userId, String accountName, String accountType,
+    public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType,
             PersistableBundle accountOptions, boolean persist) {
         checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data");
         synchronized (mPackagesLock) {
@@ -3881,20 +3898,20 @@
      * @param userId
      * @return whether the user has been initialized yet
      */
-    boolean isUserInitialized(int userId) {
+    boolean isUserInitialized(@UserIdInt int userId) {
         return mLocalService.isUserInitialized(userId);
     }
 
     private class LocalService extends UserManagerInternal {
         @Override
-        public void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
-                boolean isDeviceOwner, int cameraRestrictionScope) {
+        public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
+                @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
             UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId, restrictions,
                 isDeviceOwner, cameraRestrictionScope);
         }
 
         @Override
-        public Bundle getBaseUserRestrictions(int userId) {
+        public Bundle getBaseUserRestrictions(@UserIdInt int userId) {
             synchronized (mRestrictionsLock) {
                 return mBaseUserRestrictions.get(userId);
             }
@@ -3902,7 +3919,7 @@
 
         @Override
         public void setBaseUserRestrictionsByDpmsForMigration(
-                int userId, Bundle baseRestrictions) {
+                @UserIdInt int userId, Bundle baseRestrictions) {
             synchronized (mRestrictionsLock) {
                 if (updateRestrictionsIfNeededLR(
                         userId, new Bundle(baseRestrictions), mBaseUserRestrictions)) {
@@ -3921,7 +3938,7 @@
         }
 
         @Override
-        public boolean getUserRestriction(int userId, String key) {
+        public boolean getUserRestriction(@UserIdInt int userId, String key) {
             return getUserRestrictions(userId).getBoolean(key);
         }
 
@@ -3947,14 +3964,14 @@
         }
 
         @Override
-        public void setUserManaged(int userId, boolean isManaged) {
+        public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
             synchronized (mUsersLock) {
                 mIsUserManaged.put(userId, isManaged);
             }
         }
 
         @Override
-        public void setUserIcon(int userId, Bitmap bitmap) {
+        public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
             long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mPackagesLock) {
@@ -4011,7 +4028,7 @@
         }
 
         @Override
-        public void onEphemeralUserStop(int userId) {
+        public void onEphemeralUserStop(@UserIdInt int userId) {
             synchronized (mUsersLock) {
                UserInfo userInfo = getUserInfoLU(userId);
                if (userInfo != null && userInfo.isEphemeral()) {
@@ -4040,26 +4057,26 @@
         }
 
         @Override
-        public boolean removeUserEvenWhenDisallowed(int userId) {
+        public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
             return removeUserUnchecked(userId);
         }
 
         @Override
-        public boolean isUserRunning(int userId) {
+        public boolean isUserRunning(@UserIdInt int userId) {
             synchronized (mUserStates) {
                 return mUserStates.get(userId, -1) >= 0;
             }
         }
 
         @Override
-        public void setUserState(int userId, int userState) {
+        public void setUserState(@UserIdInt int userId, int userState) {
             synchronized (mUserStates) {
                 mUserStates.put(userId, userState);
             }
         }
 
         @Override
-        public void removeUserState(int userId) {
+        public void removeUserState(@UserIdInt int userId) {
             synchronized (mUserStates) {
                 mUserStates.delete(userId);
             }
@@ -4071,7 +4088,7 @@
         }
 
         @Override
-        public boolean isUserUnlockingOrUnlocked(int userId) {
+        public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
@@ -4085,7 +4102,7 @@
         }
 
         @Override
-        public boolean isUserUnlocked(int userId) {
+        public boolean isUserUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
@@ -4098,12 +4115,12 @@
         }
 
         @Override
-        public boolean isUserInitialized(int userId) {
+        public boolean isUserInitialized(@UserIdInt int userId) {
             return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
         }
 
         @Override
-        public boolean exists(int userId) {
+        public boolean exists(@UserIdInt int userId) {
             return getUserInfoNoChecks(userId) != null;
         }
 
@@ -4147,7 +4164,7 @@
         }
 
         @Override
-        public int getProfileParentId(int userId) {
+        public int getProfileParentId(@UserIdInt int userId) {
             synchronized (mUsersLock) {
                 UserInfo profileParent = getProfileParentLU(userId);
                 if (profileParent == null) {
@@ -4165,7 +4182,7 @@
         }
 
         @Override
-        public boolean hasUserRestriction(String restrictionKey, int userId) {
+        public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
             if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
                 return false;
             }
@@ -4180,6 +4197,17 @@
             }
             return userData == null ? null : userData.info;
         }
+
+        public @NonNull UserInfo[] getUserInfos() {
+            synchronized (mUsersLock) {
+                int userSize = mUsers.size();
+                UserInfo[] allInfos = new UserInfo[userSize];
+                for (int i = 0; i < userSize; i++) {
+                    allInfos[i] = mUsers.valueAt(i).info;
+                }
+                return allInfos;
+            }
+        }
     }
 
     /* Remove all the users except of the system one. */
@@ -4263,7 +4291,7 @@
      * @param userId The parent user
      * @return
      */
-    boolean hasManagedProfile(int userId) {
+    boolean hasManagedProfile(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             final int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 9948a3a..b3f1867 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -277,9 +277,6 @@
         try {
             fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
             if (fd == null || !fd.getFileDescriptor().valid()) {
-                Slog.wtf(TAG,
-                        "ParcelFileDescriptor.open returned an invalid descriptor for "
-                                + packageName + ":" + snapshotProfile + ". isNull=" + (fd == null));
                 postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
             } else {
                 postSuccess(packageName, fd, callback);
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 3e655ed..793cdd2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -190,6 +190,7 @@
     static {
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
         STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
     }
 
     private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
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 2cfd28d..b831374 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,7 +24,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+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_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
@@ -57,6 +57,7 @@
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PermissionGroupInfoFlags;
 import android.content.pm.PackageManager.PermissionInfoFlags;
@@ -67,6 +68,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Build;
@@ -650,9 +652,9 @@
     private void updatePermissionFlagsInternal(String permName, String packageName, int flagMask,
             int flagValues, int callingUid, int userId, boolean overridePolicy,
             PermissionCallback callback) {
-        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
+        if (ApplicationPackageManager.DEBUG_TRACE_PERMISSION_UPDATES
                 && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is updating flags for "
+            Log.i(TAG, "System is updating flags for " + packageName + " "
                             + permName + " for user " + userId  + " "
                             + DebugUtils.flagsToString(
                                     PackageManager.class, "FLAG_PERMISSION_", flagMask)
@@ -791,59 +793,81 @@
 
         final CheckPermissionDelegate checkPermissionDelegate;
         synchronized (mLock) {
-            if (mCheckPermissionDelegate == null)  {
-                return checkPermissionImpl(permName, pkgName, userId);
-            }
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
+        if (checkPermissionDelegate == null)  {
+            return checkPermissionImpl(permName, pkgName, userId);
+        }
         return checkPermissionDelegate.checkPermission(permName, pkgName, userId,
-                PermissionManagerService.this::checkPermissionImpl);
+                this::checkPermissionImpl);
     }
 
-    private int checkPermissionImpl(String permName, String pkgName, int userId) {
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+    private int checkPermissionImpl(@NonNull String permissionName, @NonNull String packageName,
+            @UserIdInt int userId) {
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
         if (pkg == null) {
             return PackageManager.PERMISSION_DENIED;
         }
-        return checkPermissionInternal(pkg, true, permName, userId);
+        return checkPermissionInternal(pkg, true, permissionName, true, userId)
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
-    private int checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
-            @NonNull String permissionName, @UserIdInt int userId) {
+    private boolean checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
+            @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps,
+            @UserIdInt int userId) {
         final int callingUid = getCallingUid();
         if (isPackageExplicit || pkg.mSharedUserId == null) {
             if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-                return PackageManager.PERMISSION_DENIED;
+                return false;
             }
         } else {
             if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-                return PackageManager.PERMISSION_DENIED;
+                return false;
             }
         }
 
         final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
         final PackageSetting ps = (PackageSetting) pkg.mExtras;
         if (ps == null) {
-            return PackageManager.PERMISSION_DENIED;
+            return false;
         }
         final PermissionsState permissionsState = ps.getPermissionsState();
 
-        if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+        if (checkSinglePermissionInternal(uid, permissionsState, permissionName,
+                useRequestedPermissionsForLegacyApps)) {
+            return true;
         }
 
         final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
-        if (fullerPermissionName != null
-                && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+        if (fullerPermissionName != null && checkSinglePermissionInternal(uid, permissionsState,
+                fullerPermissionName, useRequestedPermissionsForLegacyApps)) {
+            return true;
         }
 
-        return PackageManager.PERMISSION_DENIED;
+        return false;
     }
 
     private boolean checkSinglePermissionInternal(int uid,
-            @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
-        if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
+            @NonNull PermissionsState permissionsState, @NonNull String permissionName,
+            boolean useRequestedPermissionsForLegacyApps) {
+        boolean hasPermission = permissionsState.hasPermission(permissionName,
+                UserHandle.getUserId(uid));
+
+        if (!hasPermission && useRequestedPermissionsForLegacyApps
+                && mSettings.isPermissionRuntime(permissionName)) {
+            final String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            final int packageNamesSize = packageNames != null ? packageNames.length : 0;
+            for (int i = 0; i < packageNamesSize; i++) {
+                final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageNames[i]);
+                if (pkg != null && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                        && pkg.requestedPermissions.contains(permissionName)) {
+                    hasPermission = true;
+                    break;
+                }
+            }
+        }
+
+        if (!hasPermission) {
             return false;
         }
 
@@ -873,12 +897,13 @@
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
         return checkPermissionDelegate.checkUidPermission(permName, uid,
-                PermissionManagerService.this::checkUidPermissionImpl);
+                this::checkUidPermissionImpl);
     }
 
-    private int checkUidPermissionImpl(String permName, int uid) {
+    private int checkUidPermissionImpl(@NonNull String permissionName, int uid) {
         final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
-        return checkUidPermissionInternal(pkg, uid, permName);
+        return checkUidPermissionInternal(uid, pkg, permissionName, true)
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
     /**
@@ -888,24 +913,25 @@
      *
      * @see SystemConfig#getSystemPermissions()
      */
-    private int checkUidPermissionInternal(@Nullable Package pkg, int uid,
-            @NonNull String permissionName) {
+    private boolean checkUidPermissionInternal(int uid, @Nullable Package pkg,
+            @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps) {
         if (pkg != null) {
             final int userId = UserHandle.getUserId(uid);
-            return checkPermissionInternal(pkg, false, permissionName, userId);
+            return checkPermissionInternal(pkg, false, permissionName,
+                    useRequestedPermissionsForLegacyApps, userId);
         }
 
         if (checkSingleUidPermissionInternal(uid, permissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+            return true;
         }
 
         final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
         if (fullerPermissionName != null
                 && checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+            return true;
         }
 
-        return PackageManager.PERMISSION_DENIED;
+        return false;
     }
 
     private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) {
@@ -915,6 +941,17 @@
         }
     }
 
+    private int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+        boolean granted = isUidPermissionGranted(uid, permissionName);
+        // TODO: Foreground permissions.
+        return granted ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+    }
+
+    private boolean isUidPermissionGranted(int uid, @NonNull String permissionName) {
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
+        return checkUidPermissionInternal(uid, pkg, permissionName, false);
+    }
+
     @Override
     public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
         mContext.enforceCallingOrSelfPermission(
@@ -1185,7 +1222,7 @@
             boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
         if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
                 && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is granting "
+            Log.i(TAG, "System is granting " + packageName + " "
                     + permName + " for user " + userId + " on behalf of uid " + callingUid
                     + " " + mPackageManagerInt.getNameForUid(callingUid),
                     new RuntimeException());
@@ -1345,9 +1382,9 @@
     // TODO swap permission name and package name
     private void revokeRuntimePermissionInternal(String permName, String packageName,
             boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
-        if (ApplicationPackageManager.DEBUG_TRACE_GRANTS
+        if (ApplicationPackageManager.DEBUG_TRACE_PERMISSION_UPDATES
                 && ApplicationPackageManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is revoking "
+            Log.i(TAG, "System is revoking " + packageName + " "
                             + permName + " for user " + userId + " on behalf of uid " + callingUid
                             + " " + mPackageManagerInt.getNameForUid(callingUid),
                     new RuntimeException());
@@ -1477,7 +1514,7 @@
         // These are flags that can change base on user actions.
         final int userSettableMask = FLAG_PERMISSION_USER_SET
                 | FLAG_PERMISSION_USER_FIXED
-                | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+                | FLAG_PERMISSION_REVOKED_COMPAT
                 | FLAG_PERMISSION_REVIEW_REQUIRED;
 
         final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
@@ -1587,7 +1624,7 @@
             final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
             final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid);
             final int flags = (targetSdk < Build.VERSION_CODES.M && bp.isRuntime())
-                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT
                     : 0;
 
             updatePermissionFlagsInternal(
@@ -2332,10 +2369,11 @@
                         // 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.
-                        int numSplitPerms = PermissionManager.SPLIT_PERMISSIONS.size();
+                        final List<SplitPermissionInfoParcelable> permissionList =
+                                getSplitPermissions();
+                        int numSplitPerms = permissionList.size();
                         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-                            PermissionManager.SplitPermissionInfo sp =
-                                    PermissionManager.SPLIT_PERMISSIONS.get(splitPermNum);
+                            SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
                             String splitPermName = sp.getSplitPermission();
                             if (sp.getNewPermissions().contains(permName)
                                     && origPermissions.hasInstallPermission(splitPermName)) {
@@ -2498,8 +2536,8 @@
                                         wasChanged = true;
                                     }
 
-                                    if ((flags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
-                                        flags &= ~FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                    if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
+                                        flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
                                         wasChanged = true;
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized
@@ -2518,7 +2556,7 @@
                                                 bp.getSourcePackageName())) {
                                             if (!bp.isRemoved()) {
                                                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED
-                                                        | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                                        | FLAG_PERMISSION_REVOKED_COMPAT;
                                                 wasChanged = true;
                                             }
                                         }
@@ -2633,8 +2671,8 @@
                                         wasChanged = true;
                                     }
 
-                                    if ((flags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
-                                        flags &= ~FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                    if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
+                                        flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
                                         wasChanged = true;
                                     // Hard restricted permissions cannot be held.
                                     } else if (!permissionPolicyInitialized ||
@@ -2904,10 +2942,10 @@
         String pkgName = pkg.packageName;
         ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
 
-        int numSplitPerms = PermissionManager.SPLIT_PERMISSIONS.size();
+        final List<SplitPermissionInfoParcelable> permissionList = getSplitPermissions();
+        int numSplitPerms = permissionList.size();
         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-            PermissionManager.SplitPermissionInfo spi =
-                    PermissionManager.SPLIT_PERMISSIONS.get(splitPermNum);
+            SplitPermissionInfoParcelable spi = permissionList.get(splitPermNum);
 
             List<String> newPerms = spi.getNewPermissions();
             int numNewPerms = newPerms.size();
@@ -2962,8 +3000,6 @@
                                 Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
                                         + " for " + pkgName + " as split permission is also new");
                             }
-
-                            break;
                         } else {
                             // Inherit from new install or existing runtime permissions
                             inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
@@ -2977,6 +3013,12 @@
         return updatedUserIds;
     }
 
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        return PermissionManager.splitPermissionInfoListToParcelableList(
+                SystemConfig.getInstance().getSplitPermissions());
+    }
+
     private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
         boolean allowed = false;
         final int NP = PackageParser.NEW_PERMISSIONS.length;
@@ -3728,7 +3770,7 @@
         // Make sure all dynamic permissions have been assigned to a package,
         // and make sure there are no dangling permissions.
         boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName,
-                changingPkg);
+                changingPkg, callback);
 
         if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) {
             // Permission ownership has changed. This e.g. changes which packages can get signature
@@ -3781,7 +3823,8 @@
      * @return {@code true} if a permission source package might have changed
      */
     private boolean updatePermissionSourcePackage(@Nullable String packageName,
-            @Nullable PackageParser.Package pkg) {
+            @Nullable PackageParser.Package pkg,
+            final @Nullable PermissionCallback callback) {
         boolean changed = false;
 
         Set<BasePermission> needsUpdate = null;
@@ -3797,6 +3840,45 @@
                         && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing permission " + bp.getName()
                                 + " that used to be declared by " + bp.getSourcePackageName());
+                        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((Package p) -> {
+                                    final String pName = p.packageName;
+                                    final ApplicationInfo appInfo =
+                                            mPackageManagerInt.getApplicationInfo(pName, 0,
+                                                    Process.SYSTEM_UID, UserHandle.USER_SYSTEM);
+                                    if (appInfo != null
+                                            && appInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                                        return;
+                                    }
+
+                                    final String permissionName = bp.getName();
+                                    if (checkPermissionImpl(permissionName, pName, userId)
+                                            == PackageManager.PERMISSION_GRANTED) {
+                                        try {
+                                            revokeRuntimePermissionInternal(
+                                                    permissionName,
+                                                    pName,
+                                                    false,
+                                                    Process.SYSTEM_UID,
+                                                    userId,
+                                                    callback);
+                                        } catch (IllegalArgumentException e) {
+                                            Slog.e(TAG,
+                                                    "Failed to revoke "
+                                                            + permissionName
+                                                            + " from "
+                                                            + pName,
+                                                    e);
+                                        }
+                                    }
+                                });
+                            }
+                        }
                         changed = true;
                         it.remove();
                     }
@@ -4383,6 +4465,12 @@
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
             }
         }
+
+        @Override
+        public int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+            return PermissionManagerService.this.computeRuntimePermissionAppOpMode(uid,
+                    permissionName);
+        }
     }
 
     private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 04ec5ba..8f22f92 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -445,4 +445,13 @@
 
     /** Called when a new user has been created. */
     public abstract void onNewUserCreated(@UserIdInt int userId);
+
+    /**
+     * Compute an app op mode based on its runtime permission state.
+     *
+     * @param uid the uid for the app op
+     * @param permissionName the permission name for the app op
+     * @return the computed mode
+     */
+    public abstract int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName);
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index 9208032..3d8cf2d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -18,13 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.pm.PackageParser;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.DumpState;
@@ -37,7 +35,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.Set;
 
 /**
  * Permissions and other related data. This class is not meant for
@@ -249,6 +246,18 @@
         }
     }
 
+    /**
+     * Check whether a permission is runtime.
+     *
+     * @see BasePermission#isRuntime()
+     */
+    public boolean isPermissionRuntime(@NonNull String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isRuntime());
+        }
+    }
+
     public boolean isPermissionInstant(String permName) {
         synchronized (mLock) {
             final BasePermission bp = mPermissions.get(permName);
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 9cb2441..bbee393b 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -370,8 +370,7 @@
                         // Take an "interactive" bugreport.
                         MetricsLogger.action(mContext,
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        ActivityManager.getService().requestBugReport(
-                                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+                        ActivityManager.getService().requestInteractiveBugReport();
                     } catch (RemoteException e) {
                     }
                 }
@@ -388,8 +387,7 @@
             try {
                 // Take a "full" bugreport.
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestBugReport(
-                        ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index f68a06b..1bf319d 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -65,8 +65,8 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
-
 import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
+
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 
@@ -309,6 +309,8 @@
                 /* ignore */
             }
 
+            permissionControllerManager.updateUserSensitive();
+
             packageManagerInternal.setRuntimePermissionsFingerPrint(Build.FINGERPRINT, userId);
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ab531899..88b1793 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -204,6 +204,7 @@
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.policy.PhoneWindow;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
@@ -1603,7 +1604,7 @@
             mDisplayId = displayId;
         }
 
-        int handleHomeButton(WindowState win, KeyEvent event) {
+        int handleHomeButton(IBinder focusedToken, KeyEvent event) {
             final boolean keyguardOn = keyguardOn();
             final int repeatCount = event.getRepeatCount();
             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1646,18 +1647,18 @@
                 return -1;
             }
 
-            // If a system window has focus, then it doesn't make sense
-            // right now to interact with applications.
-            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
-            if (attrs != null) {
-                final int type = attrs.type;
-                if (type == TYPE_KEYGUARD_DIALOG
-                        || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+            final KeyInterceptionInfo info =
+                    mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+            if (info != null) {
+                // If a system window has focus, then it doesn't make sense
+                // right now to interact with applications.
+                if (info.layoutParamsType == TYPE_KEYGUARD_DIALOG
+                        || (info.layoutParamsPrivateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                     // the "app" is keyguard, so give it the key
                     return 0;
                 }
                 for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
-                    if (type == t) {
+                    if (info.layoutParamsType == t) {
                         // don't do anything, but also don't pass it to the app
                         return -1;
                     }
@@ -2598,8 +2599,9 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
-        final long result = interceptKeyBeforeDispatchingInner(win, event, policyFlags);
+    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
+            int policyFlags) {
+        final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags);
         final int eventDisplayId = event.getDisplayId();
         if (result == 0 && !mPerDisplayFocusEnabled
                 && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
@@ -2627,7 +2629,7 @@
         return result;
     }
 
-    private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event,
+    private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
@@ -2730,7 +2732,7 @@
                 handler = new DisplayHomeButtonHandler(displayId);
                 mDisplayHomeButtonHandlers.put(displayId, handler);
             }
-            return handler.handleHomeButton(win, event);
+            return handler.handleHomeButton(focusedToken, event);
         } else if (keyCode == KeyEvent.KEYCODE_MENU) {
             // Hijack modified menu keys for debugging features
             final int chordBug = KeyEvent.META_SHIFT_ON;
@@ -3120,8 +3122,7 @@
                 || Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
             try {
-                ActivityManager.getService()
-                        .requestBugReport(ActivityManager.BUGREPORT_OPTION_FULL);
+                ActivityManager.getService().requestFullBugReport();
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error taking bugreport", e);
             }
@@ -3131,10 +3132,15 @@
     // TODO(b/117479243): handle it in InputPolicy
     /** {@inheritDoc} */
     @Override
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+    public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
         // Note: This method is only called if the initial down was unhandled.
         if (DEBUG_INPUT) {
-            Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction()
+            final KeyInterceptionInfo info =
+                    mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+            final String title = info == null ? "<unknown>" : info.windowTitle;
+            Slog.d(TAG, "Unhandled key: inputToken=" + focusedToken
+                    + ", title=" + title
+                    + ", action=" + event.getAction()
                     + ", flags=" + event.getFlags()
                     + ", keyCode=" + event.getKeyCode()
                     + ", scanCode=" + event.getScanCode()
@@ -3173,7 +3179,7 @@
                         event.getDeviceId(), event.getScanCode(),
                         flags, event.getSource(), event.getDisplayId(), null);
 
-                if (!interceptFallback(win, fallbackEvent, policyFlags)) {
+                if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) {
                     fallbackEvent.recycle();
                     fallbackEvent = null;
                 }
@@ -3197,11 +3203,12 @@
         return fallbackEvent;
     }
 
-    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
+    private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
+            int policyFlags) {
         int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
         if ((actions & ACTION_PASS_TO_USER) != 0) {
             long delayMillis = interceptKeyBeforeDispatching(
-                    win, fallbackEvent, policyFlags);
+                    focusedToken, fallbackEvent, policyFlags);
             if (delayMillis == 0) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 6d9c710..01250db 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -173,7 +173,7 @@
 
     /**
      * Interface to the Window Manager state associated with a particular
-     * window.  You can hold on to an instance of this interface from the call
+     * window. You can hold on to an instance of this interface from the call
      * to prepareAddWindow() until removeWindow().
      */
     public interface WindowState {
@@ -1025,7 +1025,7 @@
      * behavior for keys that can not be overridden by applications.
      * This method is called from the input thread, with no locks held.
      *
-     * @param win The window that currently has focus.  This is where the key
+     * @param focusedToken Client window token that currently has focus. This is where the key
      *            event will normally go.
      * @param event The key event.
      * @param policyFlags The policy flags associated with the key.
@@ -1034,7 +1034,7 @@
      * milliseconds by which the key dispatch should be delayed before trying
      * again.
      */
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+    long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags);
 
     /**
      * Called from the input dispatcher thread when an application did not handle
@@ -1043,14 +1043,14 @@
      * <p>Allows you to define default global behavior for keys that were not handled
      * by applications.  This method is called from the input thread, with no locks held.
      *
-     * @param win The window that currently has focus.  This is where the key
+     * @param focusedToken Client window token that currently has focus. This is where the key
      *            event will normally go.
      * @param event The key event.
      * @param policyFlags The policy flags associated with the key.
      * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
      * The caller is responsible for recycling the key event.
      */
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
+    KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags);
 
     /**
      * Called when the top focused display is changed.
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index d5adb5e..47370b6 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -1047,8 +1047,14 @@
         @Override
         public void onSensorChanged(SensorEvent event) {
             int newRotation;
+
+            int reportedRotation = (int) event.values[0];
+            if (reportedRotation < 0 || reportedRotation > 3) {
+                return;
+            }
+
             synchronized (mLock) {
-                mDesiredRotation = (int) event.values[0];
+                mDesiredRotation = reportedRotation;
                 newRotation = evaluateRotationChangeLocked();
             }
             if (newRotation >=0) {
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index 4e9b724..ed7d234 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -156,7 +156,7 @@
             final UserSwitchObserver observer = new UserSwitchObserver();
             ActivityManager.getService().registerUserSwitchObserver(observer, TAG);
         } catch (RemoteException e) {
-             // Shouldn't happen since in-process.
+            // Shouldn't happen since in-process.
         }
 
         context.getContentResolver().registerContentObserver(Settings.System.getUriFor(
@@ -172,12 +172,13 @@
     public long updateUserActivity(long nextScreenDimming) {
         if (nextScreenDimming == mLastActedOnNextScreenDimming
                 || !mIsSettingEnabled
-                || !isAttentionServiceSupported()
                 || mWindowManager.isKeyguardShowingAndNotOccluded()) {
             return nextScreenDimming;
         }
 
-        if (!serviceHasSufficientPermissions()) {
+        if (!isAttentionServiceSupported() || !serviceHasSufficientPermissions()) {
+            // Turns off adaptive sleep in settings for all users if attention service is not
+            // available. The setting itself should also be grayed out in this case.
             Settings.System.putInt(mContentResolver, ADAPTIVE_SLEEP, 0);
             return nextScreenDimming;
         }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e57f436..eb648b3 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -542,6 +542,10 @@
     // True if we in the process of performing a forceSuspend
     private boolean mForceSuspendActive;
 
+    // Transition to Doze is in progress.  We have transitioned to WAKEFULNESS_DOZING,
+    // but the DreamService has not yet been told to start (it's an async process).
+    private boolean mDozeStartInProgress;
+
     private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
         @Override
         public void onUserSwitching(int newUserId) throws RemoteException {}
@@ -1514,6 +1518,7 @@
             mLastSleepTime = eventTime;
             mLastSleepReason = reason;
             mSandmanSummoned = true;
+            mDozeStartInProgress = true;
             setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime);
 
             // Report the number of wake locks that will be cleared by going to sleep.
@@ -1601,6 +1606,10 @@
             mWakefulness = wakefulness;
             mWakefulnessChanging = true;
             mDirty |= DIRTY_WAKEFULNESS;
+
+            // This is only valid while we are in wakefulness dozing. Set to false otherwise.
+            mDozeStartInProgress &= (mWakefulness == WAKEFULNESS_DOZING);
+
             if (mNotifier != null) {
                 mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime);
             }
@@ -1631,6 +1640,9 @@
             if (mWakefulness == WAKEFULNESS_DOZING
                     && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
                 return; // wait until dream has enabled dozing
+            } else {
+                // Doze wakelock acquired (doze started) or device is no longer dozing.
+                mDozeStartInProgress = false;
             }
             if (mWakefulness == WAKEFULNESS_DOZING || mWakefulness == WAKEFULNESS_ASLEEP) {
                 logSleepTimeoutRecapturedLocked();
@@ -2309,6 +2321,10 @@
             isDreaming = false;
         }
 
+        // At this point, we either attempted to start the dream or no attempt will be made,
+        // so stop holding the display suspend blocker for Doze.
+        mDozeStartInProgress = false;
+
         // Update dream state.
         synchronized (mLock) {
             // Remember the initial battery level when the dream started.
@@ -2734,6 +2750,16 @@
         if (mScreenBrightnessBoostInProgress) {
             return true;
         }
+
+        // When we transition to DOZING, we have to keep the display suspend blocker
+        // up until the Doze service has a change to acquire the DOZE wakelock.
+        // Here we wait for mWakefulnessChanging to become false since the wakefulness
+        // transition to DOZING isn't considered "changed" until the doze wake lock is
+        // acquired.
+        if (mWakefulness == WAKEFULNESS_DOZING && mDozeStartInProgress) {
+            return true;
+        }
+
         // Let the system suspend if the screen is off or dozing.
         return false;
     }
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
new file mode 100644
index 0000000..239a425
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog;
+
+import static com.android.server.protolog.ProtoLogFileProto.LOG;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
+import static com.android.server.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
+import static com.android.server.protolog.ProtoLogFileProto.VERSION;
+import static com.android.server.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.DOUBLE_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
+import static com.android.server.protolog.ProtoLogMessage.MESSAGE_HASH;
+import static com.android.server.protolog.ProtoLogMessage.SINT64_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.STR_PARAMS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.LogDataType;
+import com.android.server.utils.TraceBuffer;
+import com.android.server.wm.ProtoLogGroup;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IllegalFormatConversionException;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * A service for the ProtoLog logging system.
+ */
+public class ProtoLogImpl {
+    private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
+
+    private static void addLogGroupEnum(IProtoLogGroup[] config) {
+        Arrays.stream(config).forEach(group -> LOG_GROUPS.put(group.name(), group));
+    }
+
+    static {
+        addLogGroupEnum(ProtoLogGroup.values());
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+                args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance()
+                .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
+    }
+
+    /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+    public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString,
+            Object... args) {
+        getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
+    }
+
+    private static final int BUFFER_CAPACITY = 1024 * 1024;
+    private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
+    private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
+    private static final String TAG = "ProtoLog";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+    static final String PROTOLOG_VERSION = "1.0.0";
+
+    private final File mLogFile;
+    private final TraceBuffer mBuffer;
+    private final ProtoLogViewerConfigReader mViewerConfig;
+
+    private boolean mProtoLogEnabled;
+    private boolean mProtoLogEnabledLockFree;
+    private final Object mProtoLogEnabledLock = new Object();
+
+    private static ProtoLogImpl sServiceInstance = null;
+
+    /**
+     * Returns the single instance of the ProtoLogImpl singleton class.
+     */
+    public static synchronized ProtoLogImpl getSingleInstance() {
+        if (sServiceInstance == null) {
+            sServiceInstance = new ProtoLogImpl(new File(LOG_FILENAME), BUFFER_CAPACITY,
+                    new ProtoLogViewerConfigReader());
+        }
+        return sServiceInstance;
+    }
+
+    @VisibleForTesting
+    public static synchronized void setSingleInstance(@Nullable ProtoLogImpl instance) {
+        sServiceInstance = instance;
+    }
+
+    @VisibleForTesting
+    public enum LogLevel {
+        DEBUG, VERBOSE, INFO, WARN, ERROR, WTF
+    }
+
+    /**
+     * Main log method, do not call directly.
+     */
+    @VisibleForTesting
+    public void log(LogLevel level, IProtoLogGroup group, int messageHash, int paramsMask,
+            @Nullable String messageString, Object[] args) {
+        if (group.isLogToProto()) {
+            logToProto(messageHash, paramsMask, args);
+        }
+        if (group.isLogToLogcat()) {
+            logToLogcat(group.getTag(), level, messageHash, messageString, args);
+        }
+    }
+
+    private void logToLogcat(String tag, LogLevel level, int messageHash,
+            @Nullable String messageString, Object[] args) {
+        String message = null;
+        if (messageString == null) {
+            messageString = mViewerConfig.getViewerString(messageHash);
+        }
+        if (messageString != null) {
+            try {
+                message = String.format(messageString, args);
+            } catch (IllegalFormatConversionException ex) {
+                Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+            }
+        }
+        if (message == null) {
+            StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+            for (Object o : args) {
+                builder.append(" ").append(o);
+            }
+            message = builder.toString();
+        }
+        passToLogcat(tag, level, message);
+    }
+
+    /**
+     * SLog wrapper.
+     */
+    @VisibleForTesting
+    public void passToLogcat(String tag, LogLevel level, String message) {
+        switch (level) {
+            case DEBUG:
+                Slog.d(tag, message);
+                break;
+            case VERBOSE:
+                Slog.v(tag, message);
+                break;
+            case INFO:
+                Slog.i(tag, message);
+                break;
+            case WARN:
+                Slog.w(tag, message);
+                break;
+            case ERROR:
+                Slog.e(tag, message);
+                break;
+            case WTF:
+                Slog.wtf(tag, message);
+                break;
+        }
+    }
+
+    private void logToProto(int messageHash, int paramsMask, Object[] args) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        try {
+            ProtoOutputStream os = new ProtoOutputStream();
+            long token = os.start(LOG);
+            os.write(MESSAGE_HASH, messageHash);
+            os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+
+            int argIndex = 0;
+            ArrayList<Long> longParams = new ArrayList<>();
+            ArrayList<Double> doubleParams = new ArrayList<>();
+            ArrayList<Boolean> booleanParams = new ArrayList<>();
+            for (Object o : args) {
+                int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+                try {
+                    switch (type) {
+                        case LogDataType.STRING:
+                            os.write(STR_PARAMS, o.toString());
+                            break;
+                        case LogDataType.LONG:
+                            longParams.add(((Number) o).longValue());
+                            break;
+                        case LogDataType.DOUBLE:
+                            doubleParams.add(((Number) o).doubleValue());
+                            break;
+                        case LogDataType.BOOLEAN:
+                            booleanParams.add((boolean) o);
+                            break;
+                    }
+                } catch (ClassCastException ex) {
+                    // Should not happen unless there is an error in the ProtoLogTool.
+                    os.write(STR_PARAMS, "(INVALID PARAMS_MASK) " + o.toString());
+                    Slog.e(TAG, "Invalid ProtoLog paramsMask", ex);
+                }
+                argIndex++;
+            }
+            if (longParams.size() > 0) {
+                os.writePackedSInt64(SINT64_PARAMS,
+                        longParams.stream().mapToLong(i -> i).toArray());
+            }
+            if (doubleParams.size() > 0) {
+                os.writePackedDouble(DOUBLE_PARAMS,
+                        doubleParams.stream().mapToDouble(i -> i).toArray());
+            }
+            if (booleanParams.size() > 0) {
+                boolean[] arr = new boolean[booleanParams.size()];
+                for (int i = 0; i < booleanParams.size(); i++) {
+                    arr[i] = booleanParams.get(i);
+                }
+                os.writePackedBool(BOOLEAN_PARAMS, arr);
+            }
+            os.end(token);
+            mBuffer.add(os);
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception while logging to proto", e);
+        }
+    }
+
+
+    @VisibleForTesting
+    ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
+        mLogFile = file;
+        mBuffer = new TraceBuffer(bufferCapacity);
+        mViewerConfig = viewerConfig;
+    }
+
+    /**
+     * Starts the logging a circular proto buffer.
+     *
+     * @param pw Print writer
+     */
+    public void startProtoLog(@Nullable PrintWriter pw) {
+        if (isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Start logging to " + mLogFile + ".");
+            mBuffer.resetBuffer();
+            mProtoLogEnabled = true;
+            mProtoLogEnabledLockFree = true;
+        }
+    }
+
+    /**
+     * Stops logging to proto.
+     *
+     * @param pw          Print writer
+     * @param writeToFile If the current buffer should be written to disk or not
+     */
+    public void stopProtoLog(@Nullable PrintWriter pw, boolean writeToFile) {
+        if (!isProtoEnabled()) {
+            return;
+        }
+        synchronized (mProtoLogEnabledLock) {
+            logAndPrintln(pw, "Stop logging to " + mLogFile + ". Waiting for log to flush.");
+            mProtoLogEnabled = mProtoLogEnabledLockFree = false;
+            if (writeToFile) {
+                writeProtoLogToFileLocked();
+                logAndPrintln(pw, "Log written to " + mLogFile + ".");
+            }
+            if (mProtoLogEnabled) {
+                logAndPrintln(pw, "ERROR: logging was re-enabled while waiting for flush.");
+                throw new IllegalStateException("logging enabled while waiting for flush.");
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} iff logging to proto is enabled.
+     */
+    public boolean isProtoEnabled() {
+        return mProtoLogEnabledLockFree;
+    }
+
+    private int setLogging(ShellCommand shell, boolean setTextLogging, boolean value) {
+        String group;
+        while ((group = shell.getNextArg()) != null) {
+            IProtoLogGroup g = LOG_GROUPS.get(group);
+            if (g != null) {
+                if (setTextLogging) {
+                    g.setLogToLogcat(value);
+                } else {
+                    g.setLogToProto(value);
+                }
+            } else {
+                logAndPrintln(shell.getOutPrintWriter(), "No IProtoLogGroup named " + group);
+                return -1;
+            }
+        }
+        return 0;
+    }
+
+    private int unknownCommand(PrintWriter pw) {
+        pw.println("Unknown command");
+        pw.println("Window manager logging options:");
+        pw.println("  start: Start proto logging");
+        pw.println("  stop: Stop proto logging");
+        pw.println("  enable [group...]: Enable proto logging for given groups");
+        pw.println("  disable [group...]: Disable proto logging for given groups");
+        pw.println("  enable-text [group...]: Enable logcat logging for given groups");
+        pw.println("  disable-text [group...]: Disable logcat logging for given groups");
+        return -1;
+    }
+
+    /**
+     * Responds to a shell command.
+     */
+    public int onShellCommand(ShellCommand shell) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        String cmd = shell.getNextArg();
+        if (cmd == null) {
+            return unknownCommand(pw);
+        }
+        switch (cmd) {
+            case "start":
+                startProtoLog(pw);
+                return 0;
+            case "stop":
+                stopProtoLog(pw, true);
+                return 0;
+            case "status":
+                logAndPrintln(pw, getStatus());
+                return 0;
+            case "enable":
+                return setLogging(shell, false, true);
+            case "enable-text":
+                mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+                return setLogging(shell, true, true);
+            case "disable":
+                return setLogging(shell, false, false);
+            case "disable-text":
+                return setLogging(shell, true, false);
+            default:
+                return unknownCommand(pw);
+        }
+    }
+
+    /**
+     * Returns a human-readable ProtoLog status text.
+     */
+    public String getStatus() {
+        return "ProtoLog status: "
+                + ((isProtoEnabled()) ? "Enabled" : "Disabled")
+                + "\nEnabled log groups: \n  Proto: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToProto())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\n  Logcat: "
+                + LOG_GROUPS.values().stream().filter(
+                    it -> it.isEnabled() && it.isLogToLogcat())
+                .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+                + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber();
+    }
+
+    /**
+     * Writes the log buffer to a new file for the bugreport.
+     *
+     * This method is synchronized with {@code #startProtoLog(PrintWriter)} and
+     * {@link #stopProtoLog(PrintWriter, boolean)}.
+     */
+    public void writeProtoLogToFile() {
+        synchronized (mProtoLogEnabledLock) {
+            writeProtoLogToFileLocked();
+        }
+    }
+
+    private void writeProtoLogToFileLocked() {
+        try {
+            long offset =
+                    (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
+            ProtoOutputStream proto = new ProtoOutputStream();
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            proto.write(VERSION, PROTOLOG_VERSION);
+            proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
+            mBuffer.writeTraceToFile(mLogFile, proto);
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to write buffer to file", e);
+        }
+    }
+
+
+    static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+        Slog.i(TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
+}
+
diff --git a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
new file mode 100644
index 0000000..4944217
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog;
+
+import static com.android.server.protolog.ProtoLogImpl.logAndPrintln;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Handles loading and parsing of ProtoLog viewer configuration.
+ */
+public class ProtoLogViewerConfigReader {
+    private Map<Integer, String> mLogMessageMap = null;
+
+    /** Returns message format string for its hash or null if unavailable. */
+    public synchronized String getViewerString(int messageHash) {
+        if (mLogMessageMap != null) {
+            return mLogMessageMap.get(messageHash);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
+     */
+    public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
+        if (mLogMessageMap != null) {
+            return;
+        }
+        try {
+            InputStreamReader config = new InputStreamReader(
+                    new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
+            BufferedReader reader = new BufferedReader(config);
+            StringBuilder builder = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                builder.append(line).append('\n');
+            }
+            reader.close();
+            JSONObject json = new JSONObject(builder.toString());
+            JSONObject messages = json.getJSONObject("messages");
+
+            mLogMessageMap = new TreeMap<>();
+            Iterator it = messages.keys();
+            while (it.hasNext()) {
+                String key = (String) it.next();
+                try {
+                    int hash = Integer.parseInt(key);
+                    JSONObject val = messages.getJSONObject(key);
+                    String msg = val.getString("message");
+                    mLogMessageMap.put(hash, msg);
+                } catch (NumberFormatException expected) {
+                    // Not a messageHash - skip it
+                }
+            }
+            logAndPrintln(pw, "Loaded " + mLogMessageMap.size() + " log definitions from "
+                    + viewerConfigFilename);
+        } catch (FileNotFoundException e) {
+            logAndPrintln(pw, "Unable to load log definitions: File "
+                    + viewerConfigFilename + " not found." + e);
+        } catch (IOException e) {
+            logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+                    + viewerConfigFilename + ". " + e);
+        } catch (JSONException e) {
+            logAndPrintln(pw,
+                    "Unable to load log definitions: JSON parsing exception while reading "
+                            + viewerConfigFilename + ". " + e);
+        }
+    }
+
+    /**
+     * Returns the number of loaded log definitions kept in memory.
+     */
+    public synchronized int knownViewerStringsNumber() {
+        if (mLogMessageMap != null) {
+            return mLogMessageMap.size();
+        }
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
new file mode 100644
index 0000000..7bb27b2
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+/**
+ * Error while converting a bitmask representing a list of LogDataTypes.
+ */
+public class BitmaskConversionException extends RuntimeException {
+    BitmaskConversionException(String msg) {
+        super(msg);
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
new file mode 100644
index 0000000..2c65341
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+/**
+ * Defines a log group configuration object for ProtoLog. Should be implemented as en enum.
+ */
+public interface IProtoLogGroup {
+    /**
+     * if false all log statements for this group are excluded from compilation,
+     */
+    boolean isEnabled();
+
+    /**
+     * is binary logging enabled for the group.
+     */
+    boolean isLogToProto();
+
+    /**
+     * is text logging enabled for the group.
+     */
+    boolean isLogToLogcat();
+
+    /**
+     * returns true is any logging is enabled for this group.
+     */
+    default boolean isLogToAny() {
+        return isLogToLogcat() || isLogToProto();
+    }
+
+    /**
+     * returns the name of the source of the logged message
+     */
+    String getTag();
+
+    /**
+     * set binary logging for this group.
+     */
+    void setLogToProto(boolean logToProto);
+
+    /**
+     * set text logging for this group.
+     */
+    void setLogToLogcat(boolean logToLogcat);
+
+    /**
+     * returns name of the logging group.
+     */
+    String name();
+}
diff --git a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
new file mode 100644
index 0000000..947bf98
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+/**
+ * Unsupported/invalid message format string error.
+ */
+public class InvalidFormatStringException extends RuntimeException {
+    public InvalidFormatStringException(String message) {
+        super(message);
+    }
+
+    public InvalidFormatStringException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/LogDataType.java b/services/core/java/com/android/server/protolog/common/LogDataType.java
new file mode 100644
index 0000000..e73b41a
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/LogDataType.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a type of logged data encoded in the proto.
+ */
+public class LogDataType {
+    // When updating this list make sure to update bitmask conversion methods accordingly.
+    // STR type should be the first in the enum in order to be the default type.
+    public static final int STRING = 0b00;
+    public static final int LONG = 0b01;
+    public static final int DOUBLE = 0b10;
+    public static final int BOOLEAN = 0b11;
+
+    private static final int TYPE_WIDTH = 2;
+    private static final int TYPE_MASK = 0b11;
+
+    /**
+     * Creates a bitmask representing a list of data types.
+     */
+    public static int logDataTypesToBitMask(List<Integer> types) {
+        if (types.size() > 16) {
+            throw new BitmaskConversionException("Too many log call parameters "
+                    + "- max 16 parameters supported");
+        }
+        int mask = 0;
+        for (int i = 0; i < types.size(); i++) {
+            int x = types.get(i);
+            mask = mask | (x << (i * TYPE_WIDTH));
+        }
+        return mask;
+    }
+
+    /**
+     * Decodes a bitmask to a list of LogDataTypes of provided length.
+     */
+    public static int bitmaskToLogDataType(int bitmask, int index) {
+        if (index > 16) {
+            throw new BitmaskConversionException("Max 16 parameters allowed");
+        }
+        return (bitmask >> (index * TYPE_WIDTH)) & TYPE_MASK;
+    }
+
+    /**
+     * Creates a list of LogDataTypes from a message format string.
+     */
+    public static List<Integer> parseFormatString(String messageString) {
+        ArrayList<Integer> types = new ArrayList<>();
+        for (int i = 0; i < messageString.length(); ) {
+            if (messageString.charAt(i) == '%') {
+                if (i + 1 >= messageString.length()) {
+                    throw new InvalidFormatStringException("Invalid format string in config");
+                }
+                switch (messageString.charAt(i + 1)) {
+                    case 'b':
+                        types.add(LogDataType.BOOLEAN);
+                        break;
+                    case 'd':
+                    case 'o':
+                    case 'x':
+                        types.add(LogDataType.LONG);
+                        break;
+                    case 'f':
+                    case 'e':
+                    case 'g':
+                        types.add(LogDataType.DOUBLE);
+                        break;
+                    case 's':
+                        types.add(LogDataType.STRING);
+                        break;
+                    case '%':
+                        break;
+                    default:
+                        throw new InvalidFormatStringException("Invalid format string field"
+                                + " %${messageString[i + 1]}");
+                }
+                i += 2;
+            } else {
+                i += 1;
+            }
+        }
+        return types;
+    }
+}
diff --git a/services/core/java/com/android/server/protolog/common/ProtoLog.java b/services/core/java/com/android/server/protolog/common/ProtoLog.java
new file mode 100644
index 0000000..b631bcb
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/ProtoLog.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+/**
+ * ProtoLog API - exposes static logging methods. Usage of this API is similar
+ * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
+ * a messageString, which is a format string for the log message (has to be a string literal or
+ * a concatenation of string literals) and a vararg array of parameters for the formatter.
+ *
+ * The syntax for the message string is a subset of {@code java.util.Formatter} syntax.
+ * Supported conversions:
+ * %b - boolean
+ * %d, %o and %x - integral type (Short, Integer or Long)
+ * %f, %e and %g - floating point type (Float or Double)
+ * %s - string
+ * %% - a literal percent character
+ * The width and precision modifiers are supported, argument_index and flags are not.
+ *
+ * Methods in this class are stubs, that are replaced by optimised versions by the ProtoLogTool
+ * during build.
+ */
+public class ProtoLog {
+    /**
+     * DEBUG level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void d(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * VERBOSE level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void v(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * INFO level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void i(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * WARNING level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void w(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * ERROR level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void e(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+
+    /**
+     * WHAT A TERRIBLE FAILURE level log.
+     *
+     * @param group         {@code IProtoLogGroup} controlling this log call.
+     * @param messageString constant format string for the logged message.
+     * @param args          parameters to be used with the format string.
+     */
+    public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
+        // Stub, replaced by the ProtoLogTool.
+        throw new UnsupportedOperationException(
+                "ProtoLog calls MUST be processed with ProtoLogTool");
+    }
+}
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index cae09ea3..3f9cc83 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -23,17 +23,12 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -56,6 +51,8 @@
      * {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
      * snapshot folders.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void snapshotAppData(
             int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
         for (int user : userIds) {
@@ -92,6 +89,8 @@
      *         to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
      *         userId} to the list of pending backups or restores.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int userId, int appId, String seInfo) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -135,6 +134,8 @@
      * Deletes an app data snapshot with a given {@code rollbackId} for a specified package
      * {@code packageName} for a given {@code user}.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int user) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -156,141 +157,68 @@
     }
 
     /**
-     * Computes the list of pending backups for {@code userId} given lists of rollbacks.
-     * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
-     * with their corresponding {@code PackageRollbackInfo}.
+     * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
+     * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
+     * of the CE user data snapshot.
      *
-     * @return the list of rollbacks that have pending backups. Note that some of the
-     *         backups won't be performed, because they might be counteracted by pending restores.
+     * @return true if any backups or restores were found for the userId
      */
-    private static List<Rollback> computePendingBackups(int userId,
-            Map<String, PackageRollbackInfo> pendingBackupPackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final IntArray pendingBackupUsers = info.getPendingBackups();
-                if (pendingBackupUsers != null) {
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        pendingBackupPackages.put(info.getPackageName(), info);
-                        if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
-                            rollbacksWithPendingBackups.add(rollback);
-                        }
-                    }
+    @GuardedBy("rollback.getLock")
+    boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
+        boolean foundBackupOrRestore = false;
+        for (PackageRollbackInfo info : rollback.info.getPackages()) {
+            boolean hasPendingBackup = false;
+            boolean hasPendingRestore = false;
+            final IntArray pendingBackupUsers = info.getPendingBackups();
+            if (pendingBackupUsers != null) {
+                if (pendingBackupUsers.indexOf(userId) != -1) {
+                    hasPendingBackup = true;
+                    foundBackupOrRestore = true;
                 }
             }
-        }
-        return rollbacksWithPendingBackups;
-    }
 
-    /**
-     * Computes the list of pending restores for {@code userId} given lists of rollbacks.
-     * Packages pending restore are added to {@code pendingRestores} along with their corresponding
-     * {@code PackageRollbackInfo}.
-     *
-     * @return the list of rollbacks that have pending restores. Note that some of the
-     *         restores won't be performed, because they might be counteracted by pending backups.
-     */
-    private static List<Rollback> computePendingRestores(int userId,
-            Map<String, PackageRollbackInfo> pendingRestorePackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final RestoreInfo ri = info.getRestoreInfo(userId);
-                if (ri != null) {
-                    pendingRestorePackages.put(info.getPackageName(), info);
-                    if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
-                        rollbacksWithPendingRestores.add(rollback);
-                    }
-                }
+            RestoreInfo ri = info.getRestoreInfo(userId);
+            if (ri != null) {
+                hasPendingRestore = true;
+                foundBackupOrRestore = true;
             }
-        }
 
-        return rollbacksWithPendingRestores;
-    }
-
-    /**
-     * Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
-     * with pending backups, updates the {@code Rollback} instance with a mapping from
-     * {@code userId} to inode of the CE user data snapshot.
-     *
-     * @return the set of rollbacks with changes that should be stored on disk.
-     */
-    public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
-            List<Rollback> rollbacks) {
-
-        final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
-        final List<Rollback> pendingBackups = computePendingBackups(userId,
-                pendingBackupPackages, rollbacks);
-
-        final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
-        final List<Rollback> pendingRestores = computePendingRestores(userId,
-                pendingRestorePackages, rollbacks);
-
-        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
-        // request to backup data and the request to restore it.
-        Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
-                pendingBackupPackages.entrySet().iterator();
-        while (iter.hasNext()) {
-            PackageRollbackInfo backupPackage = iter.next().getValue();
-            PackageRollbackInfo restorePackage =
-                    pendingRestorePackages.get(backupPackage.getPackageName());
-            if (restorePackage != null) {
-                backupPackage.removePendingBackup(userId);
-                backupPackage.removePendingRestoreInfo(userId);
-                iter.remove();
-                pendingRestorePackages.remove(backupPackage.getPackageName());
+            if (hasPendingBackup && hasPendingRestore) {
+                // Remove unnecessary backup, i.e. when user did not unlock their phone between the
+                // request to backup data and the request to restore it.
+                info.removePendingBackup(userId);
+                info.removePendingRestoreInfo(userId);
+                continue;
             }
-        }
 
-        if (!pendingBackupPackages.isEmpty()) {
-            for (Rollback rollback : pendingBackups) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final IntArray pendingBackupUsers = info.getPendingBackups();
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        try {
-                            long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
-                                    userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.putCeSnapshotInode(userId, ceSnapshotInode);
-                            pendingBackupUsers.remove(idx);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG,
-                                    "Unable to create app data snapshot for: "
+            if (hasPendingBackup) {
+                int idx = pendingBackupUsers.indexOf(userId);
+                try {
+                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
+                            userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.putCeSnapshotInode(userId, ceSnapshotInode);
+                    pendingBackupUsers.remove(idx);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG,
+                            "Unable to create app data snapshot for: "
                                     + info.getPackageName() + ", userId: " + userId, ie);
-                        }
-                    }
+                }
+            }
+
+            if (hasPendingRestore) {
+                try {
+                    mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
+                            ri.seInfo, userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.removeRestoreInfo(ri);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG, "Unable to restore app data snapshot for: "
+                            + info.getPackageName(), ie);
                 }
             }
         }
-
-        if (!pendingRestorePackages.isEmpty()) {
-            for (Rollback rollback : pendingRestores) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final RestoreInfo ri = info.getRestoreInfo(userId);
-                    if (ri != null) {
-                        try {
-                            mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
-                                    ri.seInfo, userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.removeRestoreInfo(ri);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG, "Unable to restore app data snapshot for: "
-                                    + info.getPackageName(), ie);
-                        }
-                    }
-                }
-            }
-        }
-
-        final Set<Rollback> changed = new HashSet<>(pendingBackups);
-        changed.addAll(pendingRestores);
-        return changed;
+        return foundBackupOrRestore;
     }
 
     /**
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 0d5746b..2dc4951 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -18,19 +18,27 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.ParseException;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.List;
 
 
 /**
- * Information about a rollback available for a set of atomically installed
- * packages.
+ * Information about a rollback available for a set of atomically installed packages.
+ *
+ * <p>When accessing the state of a Rollback object, the caller is responsible for synchronization.
+ * The lock object provided by {@link #getLock} should be acquired when accessing any of the mutable
+ * state of a Rollback, including from the {@link RollbackInfo} and any of the
+ * {@link PackageRollbackInfo} objects held within.
  */
 class Rollback {
     @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
@@ -58,14 +66,24 @@
     static final int ROLLBACK_STATE_COMMITTED = 3;
 
     /**
-     * The rollback info for this rollback.
+     * The session ID for the staged session if this rollback data represents a staged session,
+     * {@code -1} otherwise.
      */
+    private final int mStagedSessionId;
+
+    /**
+     * The rollback info for this rollback.
+     *
+     * <p>Any access to this field that touches any mutable state should be synchronized on
+     * {@link #getLock}.
+     */
+    @GuardedBy("getLock")
     public final RollbackInfo info;
 
     /**
      * The directory where the rollback data is stored.
      */
-    public final File backupDir;
+    private final File mBackupDir;
 
     /**
      * The time when the upgrade occurred, for purposes of expiring
@@ -74,32 +92,36 @@
      * The timestamp is not applicable for all rollback states, but we make
      * sure to keep it non-null to avoid potential errors there.
      */
-    public @NonNull Instant timestamp;
-
-    /**
-     * The session ID for the staged session if this rollback data represents a staged session,
-     * {@code -1} otherwise.
-     */
-    public final int stagedSessionId;
+    @GuardedBy("mLock")
+    private @NonNull Instant mTimestamp;
 
     /**
      * The current state of the rollback.
      * ENABLING, AVAILABLE, or COMMITTED.
      */
-    public @RollbackState int state;
+    @GuardedBy("mLock")
+    private @RollbackState int mState;
 
     /**
      * The id of the post-reboot apk session for a staged install, if any.
      */
-    public int apkSessionId = -1;
+    @GuardedBy("mLock")
+    private int mApkSessionId = -1;
 
     /**
      * True if we are expecting the package manager to call restoreUserData
      * for this rollback because it has just been committed but the rollback
      * has not yet been fully applied.
      */
-    // NOTE: All accesses to this field are from the RollbackManager handler thread.
-    public boolean restoreUserDataInProgress = false;
+    @GuardedBy("mLock")
+    private boolean mRestoreUserDataInProgress = false;
+
+    /**
+     * Lock object to guard all access to Rollback state.
+     *
+     * @see #getLock
+     */
+    private final Object mLock = new Object();
 
     /**
      * Constructs a new, empty Rollback instance.
@@ -114,10 +136,10 @@
                 /* isStaged */ stagedSessionId != -1,
                 /* causePackages */ new ArrayList<>(),
                 /* committedSessionId */ -1);
-        this.backupDir = backupDir;
-        this.stagedSessionId = stagedSessionId;
-        this.state = ROLLBACK_STATE_ENABLING;
-        this.timestamp = Instant.now();
+        mBackupDir = backupDir;
+        mStagedSessionId = stagedSessionId;
+        mState = ROLLBACK_STATE_ENABLING;
+        mTimestamp = Instant.now();
     }
 
     /**
@@ -126,21 +148,195 @@
     Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
             @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
         this.info = info;
-        this.backupDir = backupDir;
-        this.timestamp = timestamp;
-        this.stagedSessionId = stagedSessionId;
-        this.state = state;
-        this.apkSessionId = apkSessionId;
-        this.restoreUserDataInProgress = restoreUserDataInProgress;
+        mBackupDir = backupDir;
+        mTimestamp = timestamp;
+        mStagedSessionId = stagedSessionId;
+        mState = state;
+        mApkSessionId = apkSessionId;
+        mRestoreUserDataInProgress = restoreUserDataInProgress;
+    }
+
+    /**
+     * Returns a lock object that should be acquired before accessing any Rollback state from
+     * {@link RollbackManagerServiceImpl}.
+     *
+     * <p>Note that while holding this lock, the lock for {@link RollbackManagerServiceImpl} should
+     * not be acquired (but it is ok to acquire this lock while already holding the lock for that
+     * class).
+     */
+    // TODO(b/136241838): Move rollback functionality into this class and synchronize on the lock
+    // internally. Remove this method once this has been done for all cases.
+    Object getLock() {
+        return mLock;
     }
 
     /**
      * Whether the rollback is for rollback of a staged install.
      */
-    public boolean isStaged() {
+    @GuardedBy("getLock")
+    boolean isStaged() {
         return info.isStaged();
     }
 
+    /**
+     * Returns the directory in which rollback data should be stored.
+     */
+    File getBackupDir() {
+        return mBackupDir;
+    }
+
+    /**
+     * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
+     */
+    @GuardedBy("getLock")
+    Instant getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * Sets the time at which upgrade occurred.
+     */
+    @GuardedBy("getLock")
+    void setTimestamp(Instant timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * Returns the session ID for the staged session if this rollback data represents a staged
+     * session, or {@code -1} otherwise.
+     */
+    int getStagedSessionId() {
+        return mStagedSessionId;
+    }
+
+    /**
+     * Returns true if the rollback is in the ENABLING state.
+     */
+    @GuardedBy("getLock")
+    boolean isEnabling() {
+        return mState == ROLLBACK_STATE_ENABLING;
+    }
+
+    /**
+     * Returns true if the rollback is in the AVAILABLE state.
+     */
+    @GuardedBy("getLock")
+    boolean isAvailable() {
+        return mState == ROLLBACK_STATE_AVAILABLE;
+    }
+
+    /**
+     * Returns true if the rollback is in the COMMITTED state.
+     */
+    @GuardedBy("getLock")
+    boolean isCommitted() {
+        return mState == ROLLBACK_STATE_COMMITTED;
+    }
+
+    /**
+     * Sets the state of the rollback to AVAILABLE.
+     */
+    @GuardedBy("getLock")
+    void setAvailable() {
+        mState = ROLLBACK_STATE_AVAILABLE;
+    }
+
+    /**
+     * Sets the state of the rollback to COMMITTED.
+     */
+    @GuardedBy("getLock")
+    void setCommitted() {
+        mState = ROLLBACK_STATE_COMMITTED;
+    }
+
+    /**
+     * Returns the id of the post-reboot apk session for a staged install, if any.
+     */
+    @GuardedBy("getLock")
+    int getApkSessionId() {
+        return mApkSessionId;
+    }
+
+    /**
+     * Sets the id of the post-reboot apk session for a staged install.
+     */
+    @GuardedBy("getLock")
+    void setApkSessionId(int apkSessionId) {
+        mApkSessionId = apkSessionId;
+    }
+
+    /**
+     * Returns true if we are expecting the package manager to call restoreUserData for this
+     * rollback because it has just been committed but the rollback has not yet been fully applied.
+     */
+    @GuardedBy("getLock")
+    boolean isRestoreUserDataInProgress() {
+        return mRestoreUserDataInProgress;
+    }
+
+    /**
+     * Sets whether we are expecting the package manager to call restoreUserData for this
+     * rollback because it has just been committed but the rollback has not yet been fully applied.
+     */
+    @GuardedBy("getLock")
+    void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
+        mRestoreUserDataInProgress = restoreUserDataInProgress;
+    }
+
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackage(String packageName) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}
+     * with a <i>version rolled back from</i> that is not {@code versionCode}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)
+                    && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a list containing the names of all the packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            result.add(info.getPackageName());
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list containing the names of all the apex packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getApexPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.isApex()) {
+                result.add(info.getPackageName());
+            }
+        }
+        return result;
+    }
+
     static String rollbackStateToString(@RollbackState int state) {
         switch (state) {
             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -160,7 +356,8 @@
         throw new ParseException("Invalid rollback state: " + state, 0);
     }
 
-    public String getStateAsString() {
-        return rollbackStateToString(state);
+    @GuardedBy("getLock")
+    String getStateAsString() {
+        return rollbackStateToString(mState);
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index ddbd9c9..e8e448a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -118,6 +118,10 @@
     @GuardedBy("mLock")
     private final List<Rollback> mRollbacks;
 
+    // Apk sessions from a staged session with no matching rollback.
+    @GuardedBy("mLock")
+    private final IntArray mOrphanedApkSessionIds = new IntArray();
+
     private final RollbackStore mRollbackStore;
 
     private final Context mContext;
@@ -278,8 +282,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isAvailable()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -294,8 +300,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isCommitted()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -328,8 +336,11 @@
                     Iterator<Rollback> iter = mRollbacks.iterator();
                     while (iter.hasNext()) {
                         Rollback rollback = iter.next();
-                        rollback.timestamp = rollback.timestamp.plusMillis(timeDifference);
-                        saveRollback(rollback);
+                        synchronized (rollback.getLock()) {
+                            rollback.setTimestamp(
+                                    rollback.getTimestamp().plusMillis(timeDifference));
+                            saveRollback(rollback);
+                        }
                     }
                 }
             }
@@ -354,86 +365,94 @@
         Slog.i(TAG, "Initiating rollback");
 
         Rollback rollback = getRollbackForId(rollbackId);
-        if (rollback == null || rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
+        if (rollback == null) {
             sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                     "Rollback unavailable");
             return;
         }
-
-        // Get a context for the caller to use to install the downgraded
-        // version of the package.
-        final Context context;
-        try {
-            context = mContext.createPackageContext(callerPackageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "Invalid callerPackageName");
-            return;
-        }
-
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInstaller packageInstaller = pm.getPackageInstaller();
-            PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
-                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            parentParams.setRequestDowngrade(true);
-            parentParams.setMultiPackage();
-            if (rollback.isStaged()) {
-                parentParams.setStaged();
+        synchronized (rollback.getLock()) {
+            if (!rollback.isAvailable()) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                        "Rollback unavailable");
+                return;
             }
 
-            int parentSessionId = packageInstaller.createSession(parentParams);
-            PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
+            // Get a context for the caller to use to install the downgraded
+            // version of the package.
+            final Context context;
+            try {
+                context = mContext.createPackageContext(callerPackageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "Invalid callerPackageName");
+                return;
+            }
 
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+            PackageManager pm = context.getPackageManager();
+            try {
+                PackageInstaller packageInstaller = pm.getPackageInstaller();
+                PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-                // TODO: We can't get the installerPackageName for apex
-                // (b/123920130). Is it okay to ignore the installer package
-                // for apex?
-                if (!info.isApex()) {
-                    String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
-                    if (installerPackageName != null) {
-                        params.setInstallerPackageName(installerPackageName);
-                    }
-                }
-                params.setRequestDowngrade(true);
-                params.setRequiredInstalledVersionCode(
-                        info.getVersionRolledBackFrom().getLongVersionCode());
+                parentParams.setRequestDowngrade(true);
+                parentParams.setMultiPackage();
                 if (rollback.isStaged()) {
-                    params.setStaged();
-                }
-                if (info.isApex()) {
-                    params.setInstallAsApex();
-                }
-                int sessionId = packageInstaller.createSession(params);
-                PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-                File[] packageCodePaths = RollbackStore.getPackageCodePaths(
-                        rollback, info.getPackageName());
-                if (packageCodePaths == null) {
-                    sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                            "Backup copy of package inaccessible");
-                    return;
+                    parentParams.setStaged();
                 }
 
-                for (File packageCodePath : packageCodePaths) {
-                    try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
-                                ParcelFileDescriptor.MODE_READ_ONLY)) {
-                        final long token = Binder.clearCallingIdentity();
-                        try {
-                            session.write(packageCodePath.getName(), 0, packageCodePath.length(),
-                                    fd);
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
+                int parentSessionId = packageInstaller.createSession(parentParams);
+                PackageInstaller.Session parentSession = packageInstaller.openSession(
+                        parentSessionId);
+
+                for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                    // TODO: We can't get the installerPackageName for apex
+                    // (b/123920130). Is it okay to ignore the installer package
+                    // for apex?
+                    if (!info.isApex()) {
+                        String installerPackageName =
+                                pm.getInstallerPackageName(info.getPackageName());
+                        if (installerPackageName != null) {
+                            params.setInstallerPackageName(installerPackageName);
                         }
                     }
-                }
-                parentSession.addChildSessionId(sessionId);
-            }
+                    params.setRequestDowngrade(true);
+                    params.setRequiredInstalledVersionCode(
+                            info.getVersionRolledBackFrom().getLongVersionCode());
+                    if (rollback.isStaged()) {
+                        params.setStaged();
+                    }
+                    if (info.isApex()) {
+                        params.setInstallAsApex();
+                    }
+                    int sessionId = packageInstaller.createSession(params);
+                    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+                    File[] packageCodePaths = RollbackStore.getPackageCodePaths(
+                            rollback, info.getPackageName());
+                    if (packageCodePaths == null) {
+                        sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                                "Backup copy of package inaccessible");
+                        return;
+                    }
 
-            final LocalIntentReceiver receiver = new LocalIntentReceiver(
-                    (Intent result) -> {
-                        getHandler().post(() -> {
+                    for (File packageCodePath : packageCodePaths) {
+                        try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
+                                ParcelFileDescriptor.MODE_READ_ONLY)) {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                session.write(packageCodePath.getName(), 0,
+                                        packageCodePath.length(),
+                                        fd);
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    parentSession.addChildSessionId(sessionId);
+                }
+
+                final LocalIntentReceiver receiver = new LocalIntentReceiver(
+                        (Intent result) -> getHandler().post(() -> {
 
                             int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                     PackageInstaller.STATUS_FAILURE);
@@ -446,55 +465,55 @@
                                 // TODO: Should we just kill this rollback if
                                 // commit failed? Why would we expect commit
                                 // not to fail again?
-                                synchronized (mLock) {
-                                    // TODO: Could this cause a rollback to be
-                                    // resurrected if it should otherwise have
-                                    // expired by now?
-                                    rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
-                                    rollback.restoreUserDataInProgress = false;
+                                // TODO: Could this cause a rollback to be
+                                // resurrected if it should otherwise have
+                                // expired by now?
+                                synchronized (rollback.getLock()) {
+                                    rollback.setAvailable();
+                                    rollback.setRestoreUserDataInProgress(false);
                                 }
-                                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
+                                sendFailure(statusReceiver,
+                                        RollbackManager.STATUS_FAILURE_INSTALL,
                                         "Rollback downgrade install failed: "
-                                        + result.getStringExtra(
+                                                + result.getStringExtra(
                                                 PackageInstaller.EXTRA_STATUS_MESSAGE));
                                 return;
                             }
 
-                            synchronized (mLock) {
+                            synchronized (rollback.getLock()) {
                                 if (!rollback.isStaged()) {
                                     // All calls to restoreUserData should have
                                     // completed by now for a non-staged install.
-                                    rollback.restoreUserDataInProgress = false;
+                                    rollback.setRestoreUserDataInProgress(false);
                                 }
 
                                 rollback.info.setCommittedSessionId(parentSessionId);
                                 rollback.info.getCausePackages().addAll(causePackages);
+                                RollbackStore.deletePackageCodePaths(rollback);
+                                saveRollback(rollback);
                             }
-                            mRollbackStore.deletePackageCodePaths(rollback);
-                            saveRollback(rollback);
 
                             sendSuccess(statusReceiver);
 
                             Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
 
                             for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
-                                mContext.sendBroadcastAsUser(broadcast, userInfo.getUserHandle(),
+                                mContext.sendBroadcastAsUser(broadcast,
+                                        userInfo.getUserHandle(),
                                         Manifest.permission.MANAGE_ROLLBACKS);
                             }
-                        });
-                    }
-            );
+                        })
+                );
 
-            synchronized (mLock) {
-                rollback.state = Rollback.ROLLBACK_STATE_COMMITTED;
-                rollback.restoreUserDataInProgress = true;
+                rollback.setCommitted();
+                rollback.setRestoreUserDataInProgress(true);
+                parentSession.commit(receiver.getIntentSender());
+            } catch (IOException e) {
+                Slog.e(TAG, "Rollback failed", e);
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "IOException: " + e.toString());
+                return;
             }
-            parentSession.commit(receiver.getIntentSender());
-        } catch (IOException e) {
-            Slog.e(TAG, "Rollback failed", e);
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "IOException: " + e.toString());
-            return;
         }
     }
 
@@ -530,19 +549,17 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (rollback.getLock()) {
+                    if (rollback.includesPackage(packageName)) {
                         iter.remove();
                         deleteRollback(rollback);
-                        break;
                     }
                 }
             }
             for (NewRollback newRollback : mNewRollbacks) {
-                for (PackageRollbackInfo info : newRollback.rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (newRollback.rollback.getLock()) {
+                    if (newRollback.rollback.includesPackage(packageName)) {
                         newRollback.isCancelled = true;
-                        break;
                     }
                 }
             }
@@ -564,19 +581,34 @@
     }
 
     void onUnlockUser(int userId) {
+        // In order to ensure that no package begins running while a backup or restore is taking
+        // place, onUnlockUser must remain blocked until all pending backups and restores have
+        // completed.
+        CountDownLatch latch = new CountDownLatch(1);
         getHandler().post(() -> {
             final List<Rollback> rollbacks;
             synchronized (mLock) {
                 rollbacks = new ArrayList<>(mRollbacks);
             }
 
-            final Set<Rollback> changed =
-                    mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);
-
-            for (Rollback rollback : changed) {
-                saveRollback(rollback);
+            for (int i = 0; i < rollbacks.size(); i++) {
+                Rollback rollback = rollbacks.get(i);
+                synchronized (rollback.getLock()) {
+                    if (mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(
+                            userId, rollback)) {
+                        saveRollback(rollback);
+                    }
+                }
             }
+
+            latch.countDown();
         });
+
+        try {
+            latch.await();
+        } catch (InterruptedException ie) {
+            throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
+        }
     }
 
     private void updateRollbackLifetimeDurationInMillis() {
@@ -602,17 +634,15 @@
             Set<String> apexPackageNames = new HashSet<>();
             synchronized (mLock) {
                 for (Rollback rollback : mRollbacks) {
-                    if (rollback.isStaged()) {
-                        if (rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
-                            enabling.add(rollback);
-                        } else if (rollback.restoreUserDataInProgress) {
-                            restoreInProgress.add(rollback);
-                        }
-
-                        for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                            if (info.isApex()) {
-                                apexPackageNames.add(info.getPackageName());
+                    synchronized (rollback.getLock()) {
+                        if (rollback.isStaged()) {
+                            if (rollback.isEnabling()) {
+                                enabling.add(rollback);
+                            } else if (rollback.isRestoreUserDataInProgress()) {
+                                restoreInProgress.add(rollback);
                             }
+
+                            apexPackageNames.addAll(rollback.getApexPackageNames());
                         }
                     }
                 }
@@ -620,30 +650,32 @@
 
             for (Rollback rollback : enabling) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session = installer.getSessionInfo(
-                        rollback.stagedSessionId);
-                if (session == null || session.isStagedSessionFailed()) {
-                    // TODO: Do we need to remove this from
-                    // mRollbacks, or is it okay to leave as
-                    // unavailable until the next reboot when it will go
-                    // away on its own?
-                    deleteRollback(rollback);
-                } else if (session.isStagedSessionApplied()) {
-                    makeRollbackAvailable(rollback);
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    if (session == null || session.isStagedSessionFailed()) {
+                        // TODO: Do we need to remove this from
+                        // mRollbacks, or is it okay to leave as
+                        // unavailable until the next reboot when it will go
+                        // away on its own?
+                        deleteRollback(rollback);
+                    } else if (session.isStagedSessionApplied()) {
+                        makeRollbackAvailable(rollback);
+                    }
                 }
             }
 
             for (Rollback rollback : restoreInProgress) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session = installer.getSessionInfo(
-                        rollback.stagedSessionId);
-                // TODO: What if session is null?
-                if (session != null) {
-                    if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
-                        synchronized (mLock) {
-                            rollback.restoreUserDataInProgress = false;
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    // TODO: What if session is null?
+                    if (session != null) {
+                        if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
+                            rollback.setRestoreUserDataInProgress(false);
+                            saveRollback(rollback);
                         }
-                        saveRollback(rollback);
                     }
                 }
             }
@@ -655,6 +687,11 @@
                 // hasn't actually been updated.
                 onPackageReplaced(apexPackageName);
             }
+
+            synchronized (mLock) {
+                mOrphanedApkSessionIds.clear();
+            }
+
             mPackageHealthObserver.onBootCompletedAsync();
         });
     }
@@ -667,24 +704,19 @@
     private void onPackageReplaced(String packageName) {
         // TODO: Could this end up incorrectly deleting a rollback for a
         // package that is about to be installed?
-        VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
+        long installedVersion = getInstalledPackageVersion(packageName);
 
         synchronized (mLock) {
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                // TODO: Should we remove rollbacks in the ENABLING state here?
-                if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE
-                        || rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
-                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                        if (info.getPackageName().equals(packageName)
-                                && !packageVersionsEqual(
-                                    info.getVersionRolledBackFrom(),
-                                    installedVersion)) {
-                            iter.remove();
-                            deleteRollback(rollback);
-                            break;
-                        }
+                synchronized (rollback.getLock()) {
+                    // TODO: Should we remove rollbacks in the ENABLING state here?
+                    if ((rollback.isEnabling() || rollback.isAvailable())
+                            && rollback.includesPackageWithDifferentVersion(packageName,
+                            installedVersion)) {
+                        iter.remove();
+                        deleteRollback(rollback);
                     }
                 }
             }
@@ -741,15 +773,18 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                if (rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
-                    continue;
-                }
-                if (!now.isBefore(
-                            rollback.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) {
-                    iter.remove();
-                    deleteRollback(rollback);
-                } else if (oldest == null || oldest.isAfter(rollback.timestamp)) {
-                    oldest = rollback.timestamp;
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isAvailable()) {
+                        continue;
+                    }
+                    if (!now.isBefore(
+                            rollback.getTimestamp()
+                                    .plusMillis(mRollbackLifetimeDurationInMillis))) {
+                        iter.remove();
+                        deleteRollback(rollback);
+                    } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
+                        oldest = rollback.getTimestamp();
+                    }
                 }
             }
         }
@@ -857,14 +892,26 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.apkSessionId == parentSession.getSessionId()) {
-                    // This is the apk session for a staged session with rollback enabled. We do not
-                    // need to create a new rollback for this session.
-                    return true;
+                synchronized (rollback.getLock()) {
+                    if (rollback.getApkSessionId() == parentSession.getSessionId()) {
+                        // This is the apk session for a staged session with rollback enabled. We do
+                        // not need to create a new rollback for this session.
+                        return true;
+                    }
                 }
             }
         }
 
+        // Check to see if this is the apk session for a staged session for which rollback was
+        // cancelled.
+        synchronized (mLock) {
+            if (mOrphanedApkSessionIds.indexOf(parentSession.getSessionId()) != -1) {
+                Slog.w(TAG, "Not enabling rollback for apk as no matching staged session "
+                        + "rollback exists");
+                return false;
+            }
+        }
+
         NewRollback newRollback;
         synchronized (mLock) {
             // See if we already have a NewRollback that contains this package
@@ -949,6 +996,7 @@
                 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
                 isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);
 
+
         try {
             ApplicationInfo appInfo = pkgInfo.applicationInfo;
             RollbackStore.backupPackageCodePath(rollback, packageName, appInfo.sourceDir);
@@ -962,7 +1010,7 @@
             return false;
         }
 
-        synchronized (mLock) {
+        synchronized (rollback.getLock()) {
             rollback.info.getPackages().add(packageRollbackInfo);
         }
         return true;
@@ -990,27 +1038,31 @@
             // staged installs
             for (int i = 0; i < mRollbacks.size(); i++) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state != Rollback.ROLLBACK_STATE_ENABLING) {
-                    continue;
-                }
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isEnabling()) {
+                        continue;
+                    }
 
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
-                        mAppDataRollbackHelper.snapshotAppData(
-                                rollback.info.getRollbackId(), info, userIds);
-                        saveRollback(rollback);
-                        break;
+                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                        if (info.getPackageName().equals(packageName)) {
+                            mAppDataRollbackHelper.snapshotAppData(
+                                    rollback.info.getRollbackId(), info, userIds);
+                            saveRollback(rollback);
+                            break;
+                        }
                     }
                 }
             }
             // non-staged installs
             PackageRollbackInfo info;
             for (NewRollback rollback : mNewRollbacks) {
-                info = getPackageRollbackInfo(rollback.rollback, packageName);
-                if (info != null) {
-                    mAppDataRollbackHelper.snapshotAppData(
-                            rollback.rollback.info.getRollbackId(), info, userIds);
-                    saveRollback(rollback.rollback);
+                synchronized (rollback.rollback.getLock()) {
+                    info = getPackageRollbackInfo(rollback.rollback, packageName);
+                    if (info != null) {
+                        mAppDataRollbackHelper.snapshotAppData(
+                                rollback.rollback.info.getRollbackId(), info, userIds);
+                        saveRollback(rollback.rollback);
+                    }
                 }
             }
         }
@@ -1023,11 +1075,13 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback candidate = mRollbacks.get(i);
-                if (candidate.restoreUserDataInProgress) {
-                    info = getPackageRollbackInfo(candidate, packageName);
-                    if (info != null) {
-                        rollback = candidate;
-                        break;
+                synchronized (candidate.getLock()) {
+                    if (candidate.isRestoreUserDataInProgress()) {
+                        info = getPackageRollbackInfo(candidate, packageName);
+                        if (info != null) {
+                            rollback = candidate;
+                            break;
+                        }
                     }
                 }
             }
@@ -1038,12 +1092,14 @@
         }
 
         for (int userId : userIds) {
-            final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
-                    rollback.info.getRollbackId(), info, userId, appId, seInfo);
+            synchronized (rollback.getLock()) {
+                final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
+                        rollback.info.getRollbackId(), info, userId, appId, seInfo);
 
-            // We've updated metadata about this rollback, so save it to flash.
-            if (changedRollback) {
-                saveRollback(rollback);
+                // We've updated metadata about this rollback, so save it to flash.
+                if (changedRollback) {
+                    saveRollback(rollback);
+                }
             }
         }
     }
@@ -1116,16 +1172,25 @@
             synchronized (mLock) {
                 for (int i = 0; i < mRollbacks.size(); ++i) {
                     Rollback candidate = mRollbacks.get(i);
-                    if (candidate.stagedSessionId == originalSessionId) {
-                        candidate.apkSessionId = apkSessionId;
+                    if (candidate.getStagedSessionId() == originalSessionId) {
                         rollback = candidate;
                         break;
                     }
                 }
+                if (rollback == null) {
+                    // Did not find rollback matching originalSessionId.
+                    Slog.e(TAG, "notifyStagedApkSession did not find rollback for session "
+                            + originalSessionId
+                            + ". Adding orphaned apk session " + apkSessionId);
+                    mOrphanedApkSessionIds.add(apkSessionId);
+                }
             }
 
             if (rollback != null) {
-                saveRollback(rollback);
+                synchronized (rollback.getLock()) {
+                    rollback.setApkSessionId(apkSessionId);
+                    saveRollback(rollback);
+                }
             }
         });
     }
@@ -1170,18 +1235,18 @@
 
     /**
      * Gets the version of the package currently installed.
-     * Returns null if the package is not currently installed.
+     * Returns -1 if the package is not currently installed.
      */
-    private VersionedPackage getInstalledPackageVersion(String packageName) {
+    private long getInstalledPackageVersion(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         PackageInfo pkgInfo = null;
         try {
             pkgInfo = getPackageInfo(packageName);
         } catch (PackageManager.NameNotFoundException e) {
-            return null;
+            return -1;
         }
 
-        return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+        return pkgInfo.getLongVersionCode();
     }
 
     /**
@@ -1236,44 +1301,49 @@
 
             if (newRollback != null) {
                 Rollback rollback = completeEnableRollback(newRollback, success);
-                if (rollback != null && !rollback.isStaged()) {
-                    makeRollbackAvailable(rollback);
+                if (rollback != null) {
+                    synchronized (rollback.getLock()) {
+                        if (!rollback.isStaged()) {
+                            makeRollbackAvailable(rollback);
+                        }
+                    }
                 }
             }
         }
     }
 
     /**
-     * Add a rollback to the list of rollbacks.
-     * This should be called after rollback has been enabled for all packages
-     * in the rollback. It does not make the rollback available yet.
+     * Add a rollback to the list of rollbacks. This should be called after rollback has been
+     * enabled for all packages in the rollback. It does not make the rollback available yet.
+     *
+     * <p>Note that no rollback-specific locks should be held when this method is called.
      *
      * @return the Rollback instance for a successfully enable-completed rollback,
      * or null on error.
      */
     private Rollback completeEnableRollback(NewRollback newRollback, boolean success) {
         Rollback rollback = newRollback.rollback;
-        if (!success) {
-            // The install session was aborted, clean up the pending install.
-            deleteRollback(rollback);
-            return null;
-        }
-        if (newRollback.isCancelled) {
-            Slog.e(TAG, "Rollback has been cancelled by PackageManager");
-            deleteRollback(rollback);
-            return null;
-        }
+        synchronized (rollback.getLock()) {
+            if (!success) {
+                // The install session was aborted, clean up the pending install.
+                deleteRollback(rollback);
+                return null;
+            }
+            if (newRollback.isCancelled) {
+                Slog.e(TAG, "Rollback has been cancelled by PackageManager");
+                deleteRollback(rollback);
+                return null;
+            }
 
-        // It's safe to access rollback.info outside a synchronized block because
-        // this is running on the handler thread and all changes to the
-        // rollback.info occur on the handler thread.
-        if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
-            Slog.e(TAG, "Failed to enable rollback for all packages in session.");
-            deleteRollback(rollback);
-            return null;
-        }
 
-        saveRollback(rollback);
+            if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
+                Slog.e(TAG, "Failed to enable rollback for all packages in session.");
+                deleteRollback(rollback);
+                return null;
+            }
+
+            saveRollback(rollback);
+        }
         synchronized (mLock) {
             // Note: There is a small window of time between when
             // the session has been committed by the package
@@ -1291,14 +1361,13 @@
         return rollback;
     }
 
+    @GuardedBy("rollback.getLock")
     private void makeRollbackAvailable(Rollback rollback) {
         // TODO: What if the rollback has since been expired, for example due
         // to a new package being installed. Won't this revive an expired
         // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
-        synchronized (mLock) {
-            rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
-            rollback.timestamp = Instant.now();
-        }
+        rollback.setAvailable();
+        rollback.setTimestamp(Instant.now());
         saveRollback(rollback);
 
         // TODO(zezeozue): Provide API to explicitly start observing instead
@@ -1306,11 +1375,7 @@
         // should document in PackageInstaller.SessionParams#setEnableRollback
         // After enabling and commiting any rollback, observe packages and
         // prepare to rollback if packages crashes too frequently.
-        List<String> packages = new ArrayList<>();
-        for (int i = 0; i < rollback.info.getPackages().size(); i++) {
-            packages.add(rollback.info.getPackages().get(i).getPackageName());
-        }
-        mPackageHealthObserver.startObservingHealth(packages,
+        mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
                 mRollbackLifetimeDurationInMillis);
         scheduleExpiration(mRollbackLifetimeDurationInMillis);
     }
@@ -1335,6 +1400,7 @@
      * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
      * a specified {@code Rollback}.
      */
+    @GuardedBy("rollback.getLock")
     private static PackageRollbackInfo getPackageRollbackInfo(Rollback rollback,
             String packageName) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
@@ -1361,6 +1427,7 @@
         throw new IllegalStateException("Failed to allocate rollback ID");
     }
 
+    @GuardedBy("rollback.getLock")
     private void deleteRollback(Rollback rollback) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
             IntArray snapshottedUsers = info.getSnapshottedUsers();
@@ -1379,6 +1446,7 @@
      * TODO: Double check we can't do a better job handling the IOException in
      * a cases where this method is called.
      */
+    @GuardedBy("rollback.getLock")
     private void saveRollback(Rollback rollback) {
         try {
             mRollbackStore.saveRollback(rollback);
@@ -1393,32 +1461,34 @@
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         synchronized (mLock) {
             for (Rollback rollback : mRollbacks) {
-                RollbackInfo info = rollback.info;
-                ipw.println(info.getRollbackId() + ":");
-                ipw.increaseIndent();
-                ipw.println("-state: " + rollback.getStateAsString());
-                ipw.println("-timestamp: " + rollback.timestamp);
-                if (rollback.stagedSessionId != -1) {
-                    ipw.println("-stagedSessionId: " + rollback.stagedSessionId);
-                }
-                ipw.println("-packages:");
-                ipw.increaseIndent();
-                for (PackageRollbackInfo pkg : info.getPackages()) {
-                    ipw.println(pkg.getPackageName()
-                            + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
-                            + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
-                }
-                ipw.decreaseIndent();
-                if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
-                    ipw.println("-causePackages:");
+                synchronized (rollback.getLock()) {
+                    RollbackInfo info = rollback.info;
+                    ipw.println(info.getRollbackId() + ":");
                     ipw.increaseIndent();
-                    for (VersionedPackage cPkg : info.getCausePackages()) {
-                        ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                    ipw.println("-state: " + rollback.getStateAsString());
+                    ipw.println("-timestamp: " + rollback.getTimestamp());
+                    if (rollback.getStagedSessionId() != -1) {
+                        ipw.println("-stagedSessionId: " + rollback.getStagedSessionId());
+                    }
+                    ipw.println("-packages:");
+                    ipw.increaseIndent();
+                    for (PackageRollbackInfo pkg : info.getPackages()) {
+                        ipw.println(pkg.getPackageName()
+                                + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
+                                + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
                     }
                     ipw.decreaseIndent();
-                    ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    if (rollback.isCommitted()) {
+                        ipw.println("-causePackages:");
+                        ipw.increaseIndent();
+                        for (VersionedPackage cPkg : info.getCausePackages()) {
+                            ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                        }
+                        ipw.decreaseIndent();
+                        ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    }
+                    ipw.decreaseIndent();
                 }
-                ipw.decreaseIndent();
             }
         }
     }
@@ -1479,7 +1549,8 @@
         }
     }
 
-    NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
+    @GuardedBy("mLock")
+    private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
         int rollbackId = allocateRollbackIdLocked();
         final Rollback rollback;
         int parentSessionId = parentSession.getSessionId();
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index b2448f6..b6d1f18 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -17,7 +17,6 @@
 package com.android.server.rollback;
 
 import static com.android.server.rollback.Rollback.rollbackStateFromString;
-import static com.android.server.rollback.Rollback.rollbackStateToString;
 
 import android.annotation.NonNull;
 import android.content.pm.VersionedPackage;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import libcore.io.IoUtils;
 
 import org.json.JSONArray;
@@ -216,7 +217,7 @@
     static void backupPackageCodePath(Rollback rollback, String packageName, String codePath)
             throws IOException {
         File sourceFile = new File(codePath);
-        File targetDir = new File(rollback.backupDir, packageName);
+        File targetDir = new File(rollback.getBackupDir(), packageName);
         targetDir.mkdirs();
         File targetFile = new File(targetDir, sourceFile.getName());
 
@@ -229,7 +230,7 @@
      * Includes the base apk and any splits. Returns null if none found.
      */
     static File[] getPackageCodePaths(Rollback rollback, String packageName) {
-        File targetDir = new File(rollback.backupDir, packageName);
+        File targetDir = new File(rollback.getBackupDir(), packageName);
         File[] files = targetDir.listFiles();
         if (files == null || files.length == 0) {
             return null;
@@ -243,7 +244,7 @@
      */
     static void deletePackageCodePaths(Rollback rollback) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
-            File targetDir = new File(rollback.backupDir, info.getPackageName());
+            File targetDir = new File(rollback.getBackupDir(), info.getPackageName());
             removeFile(targetDir);
         }
     }
@@ -251,17 +252,18 @@
     /**
      * Saves the given rollback to persistent storage.
      */
+    @GuardedBy("rollback.getLock")
     void saveRollback(Rollback rollback) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
             dataJson.put("info", rollbackInfoToJson(rollback.info));
-            dataJson.put("timestamp", rollback.timestamp.toString());
-            dataJson.put("stagedSessionId", rollback.stagedSessionId);
-            dataJson.put("state", rollbackStateToString(rollback.state));
-            dataJson.put("apkSessionId", rollback.apkSessionId);
-            dataJson.put("restoreUserDataInProgress", rollback.restoreUserDataInProgress);
+            dataJson.put("timestamp", rollback.getTimestamp().toString());
+            dataJson.put("stagedSessionId", rollback.getStagedSessionId());
+            dataJson.put("state", rollback.getStateAsString());
+            dataJson.put("apkSessionId", rollback.getApkSessionId());
+            dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress());
 
-            PrintWriter pw = new PrintWriter(new File(rollback.backupDir, "rollback.json"));
+            PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
             pw.println(dataJson.toString());
             pw.close();
         } catch (JSONException e) {
@@ -273,7 +275,7 @@
      * Removes all persistent storage associated with the given rollback.
      */
     void deleteRollback(Rollback rollback) {
-        removeFile(rollback.backupDir);
+        removeFile(rollback.getBackupDir());
     }
 
     /**
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index b1db46f..856a40f 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -26,27 +26,19 @@
 import android.util.apk.ApkSignatureVerifier;
 import android.util.apk.ByteBufferFactory;
 import android.util.apk.SignatureNotFoundException;
-import android.util.apk.VerityBuilder;
 
 import libcore.util.HexEncoding;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.FileChannel;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.DigestException;
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 
-import sun.security.pkcs.PKCS7;
-
 /** Provides fsverity related operations. */
 abstract public class VerityUtils {
     private static final String TAG = "VerityUtils";
@@ -60,8 +52,6 @@
     /** The maximum size of signature file.  This is just to avoid potential abuse. */
     private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
 
-    private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096;
-
     private static final boolean DEBUG = false;
 
     /** Returns true if the given file looks like containing an fs-verity signature. */
@@ -74,42 +64,15 @@
         return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
     }
 
-    /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */
-    public static void setUpFsverity(@NonNull String filePath, String signaturePath)
-            throws IOException, DigestException, NoSuchAlgorithmException {
-        final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath)));
-        final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes();
-        if (DEBUG) {
-            Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement "
-                    + bytesToString(expectedMeasurement));
-            Slog.d(TAG, "PKCS#7 info: " + pkcs7);
+    /** Enables fs-verity for the file with a PKCS#7 detached signature file. */
+    public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath)
+            throws IOException {
+        if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+            throw new SecurityException("Signature file is unexpectedly large: " + signaturePath);
         }
-
-        final TrackedBufferFactory bufferFactory = new TrackedBufferFactory();
-        final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath,
-                bufferFactory);
-        try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
-            FileChannel ch = raf.getChannel();
-            ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES));
-            ByteBuffer buffer = bufferFactory.getBuffer();
-
-            long offset = buffer.position();
-            long size = buffer.limit();
-            while (offset < size) {
-                long s = ch.write(buffer);
-                offset += s;
-                size -= s;
-            }
-        }
-
-        if (!Arrays.equals(expectedMeasurement, actualMeasurement)) {
-            throw new SecurityException("fs-verity measurement mismatch: "
-                    + bytesToString(actualMeasurement) + " != "
-                    + bytesToString(expectedMeasurement));
-        }
-
-        // This can fail if the public key is not already in .fs-verity kernel keyring.
-        int errno = enableFsverityNative(filePath);
+        byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath));
+        // This will fail if the public key is not already in .fs-verity kernel keyring.
+        int errno = enableFsverityNative(filePath, pkcs7Signature);
         if (errno != 0) {
             throw new IOException("Failed to enable fs-verity on " + filePath + ": "
                     + Os.strerror(errno));
@@ -131,12 +94,19 @@
         return true;
     }
 
+    private static native int enableFsverityNative(@NonNull String filePath,
+            @NonNull byte[] pkcs7Signature);
+    private static native int measureFsverityNative(@NonNull String filePath);
+
     /**
      * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
      *
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      * @return {@code SetupResult} that contains the result code, and when success, the
      *         {@code FileDescriptor} to read all the data from.
      */
+    @Deprecated
     public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
         if (DEBUG) {
             Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
@@ -173,7 +143,10 @@
 
     /**
      * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      */
+    @Deprecated
     public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
             throws NoSuchAlgorithmException, DigestException, IOException {
         return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
@@ -181,104 +154,16 @@
 
     /**
      * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      */
+    @Deprecated
     public static byte[] getVerityRootHash(@NonNull String apkPath)
             throws IOException, SignatureNotFoundException {
         return ApkSignatureVerifier.getVerityRootHash(apkPath);
     }
 
     /**
-     * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code
-     * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and
-     * extensions, including a PKCS#7 signature provided in {@code signaturePath}.
-     *
-     * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code
-     * ByteBuffer}. The data will be used outside this method via the factory itself.
-     *
-     * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which
-     *         includes SHA-256 of fs-verity descriptor and authenticated extensions.
-     */
-    private static byte[] generateFsverityMetadata(String filePath, String signaturePath,
-            @NonNull ByteBufferFactory trackedBufferFactory)
-            throws IOException, DigestException, NoSuchAlgorithmException {
-        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
-            VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree(
-                    file, trackedBufferFactory);
-
-            ByteBuffer buffer = result.verityData;
-            buffer.position(result.merkleTreeSize);
-
-            final byte[] measurement = generateFsverityDescriptorAndMeasurement(file,
-                    result.rootHash, signaturePath, buffer);
-            buffer.flip();
-            return constructFsveritySignedDataNative(measurement);
-        }
-    }
-
-    /**
-     * Generates fs-verity descriptor including the extensions to the {@code output} and returns the
-     * fs-verity measurement.
-     *
-     * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated
-     *         extensions.
-     */
-    private static byte[] generateFsverityDescriptorAndMeasurement(
-            @NonNull RandomAccessFile file, @NonNull byte[] rootHash,
-            @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)
-            throws IOException, NoSuchAlgorithmException, DigestException {
-        final short kRootHashExtensionId = 1;
-        final short kPkcs7SignatureExtensionId = 3;
-        final int origPosition = output.position();
-
-        // For generating fs-verity file measurement, which consists of the descriptor and
-        // authenticated extensions (but not unauthenticated extensions and the footer).
-        MessageDigest md = MessageDigest.getInstance("SHA-256");
-
-        // 1. Generate fs-verity descriptor.
-        final byte[] desc = constructFsverityDescriptorNative(file.length());
-        output.put(desc);
-        md.update(desc);
-
-        // 2. Generate authenticated extensions.
-        final byte[] authExt =
-                constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length);
-        output.put(authExt);
-        output.put(rootHash);
-        md.update(authExt);
-        md.update(rootHash);
-
-        // 3. Generate unauthenticated extensions.
-        ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
-        output.putShort((short) 1);  // number of unauthenticated extensions below
-        output.position(output.position() + 6);
-
-        // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be
-        // done by the caller if needed).
-        Path path = Paths.get(pkcs7SignaturePath);
-        if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
-            throw new IllegalArgumentException("Signature size is unexpectedly large: "
-                    + pkcs7SignaturePath);
-        }
-        final byte[] pkcs7Signature = Files.readAllBytes(path);
-        output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId,
-                    pkcs7Signature.length));
-        output.put(pkcs7Signature);
-
-        // 4. Generate the footer.
-        output.put(constructFsverityFooterNative(output.position() - origPosition));
-
-        return md.digest();
-    }
-
-    private static native int enableFsverityNative(@NonNull String filePath);
-    private static native int measureFsverityNative(@NonNull String filePath);
-    private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement);
-    private static native byte[] constructFsverityDescriptorNative(long fileSize);
-    private static native byte[] constructFsverityExtensionNative(short extensionId,
-            int extensionDataSize);
-    private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead);
-
-    /**
      * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
      * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
      * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
@@ -313,6 +198,11 @@
         return HexEncoding.encodeToString(bytes);
     }
 
+    /**
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
+     */
+    @Deprecated
     public static class SetupResult {
         /** Result code if verity is set up correctly. */
         private static final int RESULT_OK = 1;
@@ -401,30 +291,4 @@
             return mBuffer == null ? -1 : mBuffer.limit();
         }
     }
-
-    /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */
-    private static class TrackedBufferFactory implements ByteBufferFactory {
-        private ByteBuffer mBuffer;
-
-        @Override
-        public ByteBuffer create(int capacity) {
-            if (mBuffer != null) {
-                throw new IllegalStateException("Multiple instantiation from this factory");
-            }
-            mBuffer = ByteBuffer.allocate(capacity);
-            return mBuffer;
-        }
-
-        public ByteBuffer getBuffer() {
-            return mBuffer;
-        }
-    }
-
-    /** Round up the number to the next multiple of the divisor. */
-    private static long roundUpToNextMultiple(long number, long divisor) {
-        if (number > (Long.MAX_VALUE - divisor)) {
-            throw new IllegalArgumentException("arithmetic overflow");
-        }
-        return ((number + (divisor - 1)) / divisor) * divisor;
-    }
 }
diff --git a/services/core/java/com/android/server/stats/IonMemoryUtil.java b/services/core/java/com/android/server/stats/IonMemoryUtil.java
new file mode 100644
index 0000000..c9be96f
--- /dev/null
+++ b/services/core/java/com/android/server/stats/IonMemoryUtil.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Utility methods for reading ion memory stats. */
+final class IonMemoryUtil {
+    private static final String TAG = "IonMemoryUtil";
+
+    /** Path to debugfs file for the system ion heap. */
+    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
+
+    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
+    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
+
+    private IonMemoryUtil() {}
+
+    /**
+     * Reads size of the system ion heap from debugfs.
+     *
+     * Returns value of the total size in bytes of the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    static long readSystemIonHeapSizeFromDebugfs() {
+        return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
+     * debugfs. The returned value is in bytes.
+     */
+    @VisibleForTesting
+    static long parseIonHeapSizeFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        try {
+            return matcher.find() ? Long.parseLong(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Reads process allocation sizes on the system ion heap from debugfs.
+     *
+     * Returns values of allocation sizes in bytes on the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
+        return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses per-process allocation sizes on the ion heap from the contents of a file under
+     * /sys/kernel/debug/ion/heaps in debugfs.
+     */
+    @VisibleForTesting
+    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        final SparseArray<IonAllocations> entries = new SparseArray<>();
+        while (m.find()) {
+            try {
+                final int pid = Integer.parseInt(m.group(1));
+                final long sizeInBytes = Long.parseLong(m.group(2));
+                IonAllocations allocations = entries.get(pid);
+                if (allocations == null) {
+                    allocations = new IonAllocations();
+                    entries.put(pid, allocations);
+                }
+                allocations.pid = pid;
+                allocations.totalSizeInBytes += sizeInBytes;
+                allocations.count += 1;
+                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Failed to parse value", e);
+            }
+        }
+
+        final List<IonAllocations> result = new ArrayList<>(entries.size());
+        for (int i = 0; i < entries.size(); i++) {
+            result.add(entries.valueAt(i));
+        }
+        return result;
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read file", e);
+            return "";
+        }
+    }
+
+    /** Summary information about process ion allocations. */
+    static final class IonAllocations {
+        /** PID these allocations belong to. */
+        public int pid;
+        /** Size of all individual allocations added together. */
+        public long totalSizeInBytes;
+        /** Number of allocations. */
+        public int count;
+        /** Size of the largest allocation. */
+        public long maxSizeInBytes;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            IonAllocations that = (IonAllocations) o;
+            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
+                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
+        }
+
+        @Override
+        public String toString() {
+            return "IonAllocations{"
+                    + "pid=" + pid
+                    + ", totalSizeInBytes=" + totalSizeInBytes
+                    + ", count=" + count
+                    + ", maxSizeInBytes=" + maxSizeInBytes
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
new file mode 100644
index 0000000..d49b958
--- /dev/null
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ProcfsMemoryUtil {
+    private static final String TAG = "ProcfsMemoryUtil";
+
+    /** Path to procfs status file: /proc/pid/status. */
+    private static final String STATUS_FILE_FMT = "/proc/%d/status";
+
+    private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES =
+            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
+    private static final Pattern RSS_IN_KILOBYTES =
+            Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
+    private static final Pattern ANON_RSS_IN_KILOBYTES =
+            Pattern.compile("RssAnon:\\s*(\\d+)\\s*kB");
+    private static final Pattern SWAP_IN_KILOBYTES =
+            Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB");
+
+    private ProcfsMemoryUtil() {}
+
+    /**
+     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
+     * /proc/PID/status in kilobytes or 0 if not available.
+     */
+    static int readRssHighWaterMarkFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseVmHWMFromStatus(readFile(statusPath));
+    }
+
+    /**
+     * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
+     * returned value is in kilobytes.
+     */
+    @VisibleForTesting
+    static int parseVmHWMFromStatus(String contents) {
+        return tryParseInt(contents, RSS_HIGH_WATER_MARK_IN_KILOBYTES);
+    }
+
+    /**
+     * Reads memory stat of a process from procfs. Returns values of the VmRss, AnonRSS, VmSwap
+     * fields in /proc/pid/status in kilobytes or 0 if not available.
+     */
+    static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseMemorySnapshotFromStatus(readFile(statusPath));
+    }
+
+    @VisibleForTesting
+    static MemorySnapshot parseMemorySnapshotFromStatus(String contents) {
+        final MemorySnapshot snapshot = new MemorySnapshot();
+        snapshot.rssInKilobytes = tryParseInt(contents, RSS_IN_KILOBYTES);
+        snapshot.anonRssInKilobytes = tryParseInt(contents, ANON_RSS_IN_KILOBYTES);
+        snapshot.swapInKilobytes = tryParseInt(contents, SWAP_IN_KILOBYTES);
+        return snapshot;
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            return "";
+        }
+    }
+
+    private static int tryParseInt(String contents, Pattern pattern) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = pattern.matcher(contents);
+        try {
+            return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    static final class MemorySnapshot {
+        public int rssInKilobytes;
+        public int anonRssInKilobytes;
+        public int swapInKilobytes;
+
+        boolean isEmpty() {
+            return (anonRssInKilobytes + swapInKilobytes) == 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c76bbb0..e1a48ed 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -27,9 +27,10 @@
 import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readProcessSystemIonHeapSizesFromDebugfs;
-import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -138,9 +139,10 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.am.MemoryStatUtil.IonAllocations;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
 import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.storage.DiskStatsFileLogger;
 import com.android.server.storage.DiskStatsLoggingService;
 
@@ -1112,13 +1114,12 @@
             e.writeLong(modemInfo.getTimestamp());
             e.writeLong(modemInfo.getSleepTimeMillis());
             e.writeLong(modemInfo.getIdleTimeMillis());
-            e.writeLong(modemInfo.getTxTimeMillis()[0]);
-            e.writeLong(modemInfo.getTxTimeMillis()[1]);
-            e.writeLong(modemInfo.getTxTimeMillis()[2]);
-            e.writeLong(modemInfo.getTxTimeMillis()[3]);
-            e.writeLong(modemInfo.getTxTimeMillis()[4]);
-            e.writeLong(modemInfo.getRxTimeMillis());
-            e.writeLong(modemInfo.getEnergyUsed());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis());
+            e.writeLong(modemInfo.getReceiveTimeMillis());
             pulledData.add(e);
         }
     }
@@ -1237,15 +1238,17 @@
                 LocalServices.getService(
                         ActivityManagerInternal.class).getMemoryStateForProcesses();
         for (ProcessMemoryState managedProcess : managedProcessList) {
-            final long rssHighWaterMarkInBytes =
+            final int rssHighWaterMarkInKilobytes =
                     readRssHighWaterMarkFromProcfs(managedProcess.pid);
-            if (rssHighWaterMarkInBytes == 0) {
+            if (rssHighWaterMarkInKilobytes == 0) {
                 continue;
             }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(managedProcess.uid);
             e.writeString(managedProcess.processName);
-            e.writeLong(rssHighWaterMarkInBytes);
+            // RSS high-water mark in bytes.
+            e.writeLong((long) rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(rssHighWaterMarkInKilobytes);
             pulledData.add(e);
         }
         int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
@@ -1253,17 +1256,63 @@
             final int pid = pids[i];
             final int uid = getUidForPid(pid);
             final String processName = readCmdlineFromProcfs(pid);
-            final long rssHighWaterMarkInBytes = readRssHighWaterMarkFromProcfs(pid);
+            final int rssHighWaterMarkInKilobytes = readRssHighWaterMarkFromProcfs(pid);
+            if (rssHighWaterMarkInKilobytes == 0) {
+                continue;
+            }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(uid);
             e.writeString(processName);
-            e.writeLong(rssHighWaterMarkInBytes);
+            // RSS high-water mark in bytes.
+            e.writeLong((long) rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(rssHighWaterMarkInKilobytes);
             pulledData.add(e);
         }
         // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
         SystemProperties.set("sys.rss_hwm_reset.on", "1");
     }
 
+    private void pullProcessMemorySnapshot(
+            int tagId, long elapsedNanos, long wallClockNanos,
+            List<StatsLogEventWrapper> pulledData) {
+        List<ProcessMemoryState> managedProcessList =
+                LocalServices.getService(
+                        ActivityManagerInternal.class).getMemoryStateForProcesses();
+        for (ProcessMemoryState managedProcess : managedProcessList) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(managedProcess.uid);
+            e.writeString(managedProcess.processName);
+            e.writeInt(managedProcess.pid);
+            e.writeInt(managedProcess.oomScore);
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+        int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
+        for (int pid : pids) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(getUidForPid(pid));
+            e.writeString(readCmdlineFromProcfs(pid));
+            e.writeInt(pid);
+            e.writeInt(-1001);  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+    }
+
     private void pullSystemIonHeapSize(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2346,6 +2395,10 @@
                 pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
+                pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
             case StatsLog.SYSTEM_ION_HEAP_SIZE: {
                 pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 2df7370..60de10c 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -42,13 +42,13 @@
 import android.os.UserManager;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseLongArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.Installer;
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
new file mode 100644
index 0000000..2d36a0d
--- /dev/null
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import android.Manifest;
+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.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.ParcelFileDescriptor;
+import android.os.storage.VolumeInfo;
+import android.provider.MediaStore;
+import android.service.storage.ExternalStorageService;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Controls storage sessions for users initiated by the {@link StorageManagerService}.
+ * Each user on the device will be represented by a {@link StorageUserConnection}.
+ */
+public final class StorageSessionController {
+    private static final String TAG = "StorageSessionController";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final Callback mCallback;
+    @GuardedBy("mLock")
+    private ComponentName mExternalStorageServiceComponent;
+    @GuardedBy("mLock")
+    private final SparseArray<StorageUserConnection> mConnections = new SparseArray<>();
+
+    public StorageSessionController(Context context, Callback callback) {
+        mContext = Preconditions.checkNotNull(context);
+        mCallback = Preconditions.checkNotNull(callback);
+    }
+
+    /**
+     * Starts a storage session associated with {@code deviceFd} for {@code vol}.
+     * Does nothing if a session is already started or starting. If the user associated with
+     * {@code vol} is not yet ready, the session will be retried {@link #onUserStarted}.
+     *
+     * A session must be ended with {@link #endSession} when no longer required.
+     */
+    public void onVolumeMounted(int userId, FileDescriptor deviceFd, VolumeInfo vol) {
+        if (deviceFd == null) {
+            Slog.w(TAG, "Null device fd. Session not started for " + vol);
+            return;
+        }
+
+        // Get realpath for the fd, paths that are not /dev/null need additional
+        // setup by the ExternalStorageService before they can be ready
+        String realPath;
+        try {
+            realPath = ParcelFileDescriptor.getFile(deviceFd).getPath();
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Could not get real path from fd: " + deviceFd, e);
+            return;
+        }
+
+        if ("/dev/null".equals(realPath)) {
+            Slog.i(TAG, "Volume ready for use: " + vol);
+            return;
+        }
+
+        synchronized (mLock) {
+            StorageUserConnection connection = mConnections.get(userId);
+            if (connection == null) {
+                Slog.i(TAG, "Creating new session for vol: " + vol);
+                connection = new StorageUserConnection(mContext, userId, this);
+                mConnections.put(userId, connection);
+            }
+            try {
+                Slog.i(TAG, "Starting session for vol: " + vol);
+                connection.startSession(deviceFd, vol);
+            } catch (ExternalStorageServiceException e) {
+                Slog.e(TAG, "Failed to start session for vol: " + vol, e);
+            }
+        }
+    }
+
+    /**
+     * Ends a storage session for {@code vol}. Does nothing if the session is already
+     * ended or ending. Ending a session discards all resources associated with that session.
+     */
+    public void onVolumeUnmounted(int userId, VolumeInfo vol) {
+        synchronized (mLock) {
+            StorageUserConnection connection = mConnections.get(userId);
+            if (connection != null) {
+                Slog.i(TAG, "Ending session for vol: " + vol);
+                try {
+                    if (connection.endSession(vol)) {
+                        mConnections.remove(userId);
+                    }
+                } catch (ExternalStorageServiceException e) {
+                    Slog.e(TAG, "Failed to end session for vol: " + vol, e);
+                }
+            } else {
+                Slog.w(TAG, "Session already ended for vol: " + vol);
+            }
+        }
+    }
+
+    /** Restarts all sessions for {@code userId}. */
+    public void onUserStarted(int userId) {
+        synchronized (mLock) {
+            StorageUserConnection connection = mConnections.get(userId);
+            if (connection != null) {
+                try {
+                    Slog.i(TAG, "Restarting all sessions for user: " + userId);
+                    connection.startAllSessions();
+                } catch (ExternalStorageServiceException e) {
+                    Slog.e(TAG, "Failed to start all sessions", e);
+                }
+            } else {
+                // TODO(b/135341433): What does this mean in multi-user
+            }
+        }
+    }
+
+    /** Ends all sessions for {@code userId}. */
+    public void onUserRemoved(int userId) {
+        synchronized (mLock) {
+            StorageUserConnection connection = mConnections.get(userId);
+            if (connection != null) {
+                try {
+                    Slog.i(TAG, "Ending all sessions for user: " + userId);
+                    connection.endAllSessions();
+                    mConnections.remove(userId);
+                } catch (ExternalStorageServiceException e) {
+                    Slog.e(TAG, "Failed to end all sessions", e);
+                }
+            } else {
+                // TODO(b/135341433): What does this mean in multi-user
+            }
+        }
+    }
+
+    /** Returns the {@link ExternalStorageService} component name. */
+    @Nullable
+    public ComponentName getExternalStorageServiceComponentName() {
+        synchronized (mLock) {
+            if (mExternalStorageServiceComponent == null) {
+                ProviderInfo provider = mContext.getPackageManager().resolveContentProvider(
+                        MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                        | PackageManager.MATCH_SYSTEM_ONLY);
+
+                if (provider == null) {
+                    Slog.e(TAG, "No valid MediaStore provider found.");
+                }
+                String packageName = provider.applicationInfo.packageName;
+
+                Intent intent = new Intent(ExternalStorageService.SERVICE_INTERFACE);
+                intent.setPackage(packageName);
+                ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+                if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+                    Slog.e(TAG, "No valid ExternalStorageService component found.");
+                    return null;
+                }
+
+                ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+                ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+                if (!Manifest.permission.BIND_EXTERNAL_STORAGE_SERVICE
+                        .equals(serviceInfo.permission)) {
+                    Slog.e(TAG, name.flattenToShortString() + " does not require permission "
+                            + Manifest.permission.BIND_EXTERNAL_STORAGE_SERVICE);
+                    return null;
+                }
+                mExternalStorageServiceComponent = name;
+            }
+            return mExternalStorageServiceComponent;
+        }
+    }
+
+    /** Returns the {@link StorageManagerService} callback. */
+    public Callback getCallback() {
+        return mCallback;
+    }
+
+    /** Callback to listen to session events from the {@link StorageSessionController}. */
+    public interface Callback {
+        /** Called when a {@link StorageUserConnection} is disconnected. */
+        void onUserDisconnected(int userId);
+    }
+
+    /** Exception thrown when communication with the {@link ExternalStorageService}. */
+    public static class ExternalStorageServiceException extends Exception {
+        public ExternalStorageServiceException(Throwable cause) {
+            super(cause);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
new file mode 100644
index 0000000..ff9c900
--- /dev/null
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import static android.service.storage.ExternalStorageService.EXTRA_ERROR;
+import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE;
+import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE;
+
+import static com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
+import android.service.storage.ExternalStorageService;
+import android.service.storage.IExternalStorageService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Controls the lifecycle of the {@link ActiveConnection} to an {@link ExternalStorageService}
+ * for a user and manages storage sessions represented by a {@link Session}.
+ */
+public final class StorageUserConnection {
+    private static final String TAG = "StorageUserConnection";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final int mUserId;
+    private final StorageSessionController mSessionController;
+    private final ActiveConnection mActiveConnection = new ActiveConnection();
+    @GuardedBy("mLock") private final Map<String, Session> mSessions = new HashMap<>();
+
+    public StorageUserConnection(Context context, int userId, StorageSessionController controller) {
+        mContext = Preconditions.checkNotNull(context);
+        mUserId = Preconditions.checkArgumentNonnegative(userId);
+        mSessionController = controller;
+    }
+
+    /** Starts a session for a user */
+    public void startSession(FileDescriptor deviceFd, VolumeInfo vol)
+            throws ExternalStorageServiceException {
+        String sessionId = vol.getId();
+        String upperPath = vol.getPath().getPath();
+        String lowerPath = vol.getInternalPath().getPath();
+        Slog.i(TAG, "Starting session with id: " + sessionId + " and upperPath: " + upperPath
+                + " and lowerPath: " + lowerPath);
+        Session session = new Session(sessionId, deviceFd, upperPath, lowerPath);
+        synchronized (mLock) {
+            // TODO(b/135341433): Ensure we don't replace a session without ending the previous
+            mSessions.put(sessionId, session);
+            // TODO(b/135341433): If this fails, maybe its at boot, how to handle if not boot?
+            mActiveConnection.startSessionLocked(session);
+        }
+    }
+
+    /**
+     * Ends a session for a user.
+     *
+     * @return {@code true} if there are no more sessions for this user, {@code false} otherwise
+     **/
+    public boolean endSession(VolumeInfo vol) throws ExternalStorageServiceException {
+        synchronized (mLock) {
+            Session session = mSessions.remove(vol.getId());
+            if (session != null) {
+                mActiveConnection.endSessionLocked(session);
+                mSessions.remove(session.sessionId);
+            }
+            boolean isAllSessionsEnded = mSessions.isEmpty();
+            if (isAllSessionsEnded) {
+                mActiveConnection.close();
+            }
+            return isAllSessionsEnded;
+        }
+    }
+
+    /** Starts all available sessions for a user */
+    public void startAllSessions() throws ExternalStorageServiceException {
+        synchronized (mLock) {
+            for (Session session : mSessions.values()) {
+                mActiveConnection.startSessionLocked(session);
+            }
+        }
+    }
+
+    /** Ends all available sessions for a user */
+    public void endAllSessions() throws ExternalStorageServiceException {
+        synchronized (mLock) {
+            for (Session session : mSessions.values()) {
+                mActiveConnection.endSessionLocked(session);
+                mSessions.remove(session.sessionId);
+            }
+            mActiveConnection.close();
+        }
+    }
+
+    private final class ActiveConnection implements AutoCloseable {
+        // Lifecycle connection to the external storage service, needed to unbind.
+        // We should only try to bind if mServiceConnection is null.
+        // Non-null indicates we are connected or connecting.
+        @GuardedBy("mLock") @Nullable private ServiceConnection mServiceConnection;
+        // Binder object representing the external storage service.
+        // Non-null indicates we are connected
+        @GuardedBy("mLock") @Nullable private IExternalStorageService mRemote;
+        // Exception, if any, thrown from #startSessionLocked or #endSessionLocked
+        // Local variables cannot be referenced from a lambda expression :( so we
+        // save the exception received in the callback here. Since we guard access
+        // (and clear the exception state) with the same lock which we hold during
+        // the entire transaction, there is no risk of race.
+        @GuardedBy("mLock") @Nullable private ParcelableException mLastException;
+
+        @Override
+        public void close() {
+            synchronized (mLock) {
+                if (mServiceConnection != null) {
+                    mContext.unbindService(mServiceConnection);
+                }
+                mServiceConnection = null;
+                mRemote = null;
+            }
+        }
+
+        public void startSessionLocked(Session session) throws ExternalStorageServiceException {
+            if (mServiceConnection == null || mRemote == null) {
+                if (mServiceConnection == null) {
+                    // Not bound
+                    bindLocked();
+                } // else we are binding. In any case when we bind we'll re-start all sessions
+                return;
+            }
+
+            CountDownLatch latch = new CountDownLatch(1);
+            try {
+                mRemote.startSession(session.sessionId,
+                        FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
+                        new ParcelFileDescriptor(session.deviceFd), session.upperPath,
+                        session.lowerPath, new RemoteCallback(result ->
+                                setResultLocked(latch, result)));
+
+            } catch (RemoteException e) {
+                throw new ExternalStorageServiceException(e);
+            }
+            waitAndReturnResultLocked(latch);
+        }
+
+        public void endSessionLocked(Session session) throws ExternalStorageServiceException {
+            if (mRemote == null) {
+                // TODO(b/135341433): This assumes if there is no connection, there are no
+                // session resources held. Need to document in the ExternalStorageService
+                // API that implementors should end all sessions and clean up resources
+                // when the binding is lost, onDestroy?
+                return;
+            }
+
+            CountDownLatch latch = new CountDownLatch(1);
+            try {
+                mRemote.endSession(session.sessionId, new RemoteCallback(result ->
+                                setResultLocked(latch, result)));
+            } catch (RemoteException e) {
+                throw new ExternalStorageServiceException(e);
+            }
+            waitAndReturnResultLocked(latch);
+        }
+
+        private void setResultLocked(CountDownLatch latch, Bundle result) {
+            mLastException = result.getParcelable(EXTRA_ERROR);
+            latch.countDown();
+        }
+
+        private void waitAndReturnResultLocked(CountDownLatch latch)
+                throws ExternalStorageServiceException {
+            try {
+                // TODO(b/140025078): Call ActivityManager ANR API?
+                latch.await(20, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException(
+                        "Interrupted while waiting for ExternalStorageService result");
+            }
+            if (mLastException != null) {
+                mLastException = null;
+                try {
+                    mLastException.maybeRethrow(IOException.class);
+                } catch (IOException e) {
+                    throw new ExternalStorageServiceException(e);
+                }
+                throw new RuntimeException(mLastException);
+            }
+            mLastException = null;
+        }
+
+        private void bindLocked() {
+            ComponentName name = mSessionController.getExternalStorageServiceComponentName();
+            if (name == null) {
+                Slog.i(TAG, "Not ready to bind to the ExternalStorageService for user " + mUserId);
+                return;
+            }
+
+            ServiceConnection connection = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        Slog.i(TAG, "Service: [" + name + "] connected. User [" + mUserId + "]");
+                        handleConnection(service);
+                    }
+
+                    @Override
+                    @MainThread
+                    public void onServiceDisconnected(ComponentName name) {
+                        // Service crashed or process was killed, #onServiceConnected will be called
+                        // Don't need to re-bind.
+                        Slog.i(TAG, "Service: [" + name + "] disconnected. User [" + mUserId + "]");
+                        handleDisconnection();
+                    }
+
+                    @Override
+                    public void onBindingDied(ComponentName name) {
+                        // Application hosting service probably got updated
+                        // Need to re-bind.
+                        Slog.i(TAG, "Service: [" + name + "] died. User [" + mUserId + "]");
+                        handleDisconnection();
+                    }
+
+                    @Override
+                    public void onNullBinding(ComponentName name) {
+                        // Should never happen. Service returned null from #onBind.
+                        Slog.wtf(TAG, "Service: [" + name + "] is null. User [" + mUserId + "]");
+                    }
+
+                    private void handleConnection(IBinder service) {
+                        synchronized (mLock) {
+                            if (mServiceConnection != null) {
+                                mRemote = IExternalStorageService.Stub.asInterface(service);
+                            } else {
+                                Slog.wtf(TAG, "Service connected without a connection object??");
+                            }
+                        }
+
+                        try {
+                            startAllSessions();
+                        } catch (ExternalStorageServiceException e) {
+                            Slog.e(TAG, "Failed to start all sessions", e);
+                        }
+                    }
+
+                    private void handleDisconnection() {
+                        close();
+                        // Clear all sessions because we will need a new device fd since
+                        // StorageManagerService will reset the device mount state and #startSession
+                        // will be called for any required mounts.
+                        synchronized (mLock) {
+                            mSessions.clear();
+                        }
+                        // Notify StorageManagerService so it can restart all necessary sessions
+                        mSessionController.getCallback().onUserDisconnected(mUserId);
+                    }
+                };
+
+            Slog.i(TAG, "Binding to the ExternalStorageService for user " + mUserId);
+            // TODO(b/135341433): Verify required service flags BIND_IMPORTANT?
+            if (mContext.bindServiceAsUser(new Intent().setComponent(name), connection,
+                            Context.BIND_AUTO_CREATE, UserHandle.of(mUserId))) {
+                Slog.i(TAG, "Bound to the ExternalStorageService for user " + mUserId);
+                mServiceConnection = connection;
+                // Reset the remote, we will set when we connect
+                mRemote = null;
+            } else {
+                Slog.w(TAG, "Failed to bind to the ExternalStorageService for user " + mUserId);
+            }
+        }
+    }
+
+    private static final class Session {
+        public final String sessionId;
+        public final FileDescriptor deviceFd;
+        public final String lowerPath;
+        public final String upperPath;
+
+        Session(String sessionId, FileDescriptor deviceFd, String upperPath,
+                String lowerPath) {
+            this.sessionId = sessionId;
+            this.upperPath = upperPath;
+            this.lowerPath = lowerPath;
+            this.deviceFd = deviceFd;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
index b27d5ea..f8ffb7c 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
@@ -23,7 +23,6 @@
 import android.media.tv.ITvRemoteProvider;
 import android.media.tv.ITvRemoteServiceInput;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -49,7 +48,6 @@
     private final ComponentName mComponentName;
     private final int mUserId;
     private final int mUid;
-    private final Handler mHandler;
 
     /**
      * State guarded by mLock.
@@ -65,15 +63,14 @@
     private boolean mRunning;
     private boolean mBound;
     private Connection mActiveConnection;
-    private boolean mConnectionReady;
 
-    public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId,
-                                 int uid) {
+    TvRemoteProviderProxy(Context context, ProviderMethods provider,
+                          ComponentName componentName, int userId, int uid) {
         mContext = context;
+        mProviderMethods = provider;
         mComponentName = componentName;
         mUserId = userId;
         mUid = uid;
-        mHandler = new Handler();
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -82,11 +79,6 @@
         pw.println(prefix + "  mRunning=" + mRunning);
         pw.println(prefix + "  mBound=" + mBound);
         pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
-        pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
-    }
-
-    public void setProviderSink(ProviderMethods provider) {
-        mProviderMethods = provider;
     }
 
     public boolean hasComponentName(String packageName, String className) {
@@ -101,7 +93,7 @@
             }
 
             mRunning = true;
-            updateBinding();
+            bind();
         }
     }
 
@@ -112,31 +104,19 @@
             }
 
             mRunning = false;
-            updateBinding();
+            unbind();
         }
     }
 
     public void rebindIfDisconnected() {
         synchronized (mLock) {
-            if (mActiveConnection == null && shouldBind()) {
+            if (mActiveConnection == null && mRunning) {
                 unbind();
                 bind();
             }
         }
     }
 
-    private void updateBinding() {
-        if (shouldBind()) {
-            bind();
-        } else {
-            unbind();
-        }
-    }
-
-    private boolean shouldBind() {
-        return mRunning;
-    }
-
     private void bind() {
         if (!mBound) {
             if (DEBUG) {
@@ -208,48 +188,19 @@
         disconnect();
     }
 
-
-    private void onConnectionReady(Connection connection) {
-        synchronized (mLock) {
-            if (DEBUG) Slog.d(TAG, "onConnectionReady");
-            if (mActiveConnection == connection) {
-                if (DEBUG) Slog.d(TAG, "mConnectionReady = true");
-                mConnectionReady = true;
-            }
-        }
-    }
-
-    private void onConnectionDied(Connection connection) {
-        if (mActiveConnection == connection) {
-            if (DEBUG) Slog.d(TAG, this + ": Service connection died");
-            disconnect();
-        }
-    }
-
     private void disconnect() {
         synchronized (mLock) {
             if (mActiveConnection != null) {
-                mConnectionReady = false;
                 mActiveConnection.dispose();
                 mActiveConnection = null;
             }
         }
     }
 
-    // Provider helpers
-    public void inputBridgeConnected(IBinder token) {
-        synchronized (mLock) {
-            if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token);
-            if (mConnectionReady) {
-                mActiveConnection.onInputBridgeConnected(token);
-            }
-        }
-    }
-
-    public interface ProviderMethods {
+    interface ProviderMethods {
         // InputBridge
-        void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
-                             int width, int height, int maxPointers);
+        boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
+                                int width, int height, int maxPointers);
 
         void closeInputBridge(TvRemoteProviderProxy provider, IBinder token);
 
@@ -267,7 +218,7 @@
         void sendPointerSync(TvRemoteProviderProxy provider, IBinder token);
     }
 
-    private final class Connection implements IBinder.DeathRecipient {
+    private final class Connection {
         private final ITvRemoteProvider mTvRemoteProvider;
         private final RemoteServiceInputProvider mServiceInputProvider;
 
@@ -279,24 +230,16 @@
         public boolean register() {
             if (DEBUG) Slog.d(TAG, "Connection::register()");
             try {
-                mTvRemoteProvider.asBinder().linkToDeath(this, 0);
                 mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onConnectionReady(Connection.this);
-                    }
-                });
                 return true;
             } catch (RemoteException ex) {
-                binderDied();
+                dispose();
+                return false;
             }
-            return false;
         }
 
         public void dispose() {
             if (DEBUG) Slog.d(TAG, "Connection::dispose()");
-            mTvRemoteProvider.asBinder().unlinkToDeath(this, 0);
             mServiceInputProvider.dispose();
         }
 
@@ -310,16 +253,6 @@
             }
         }
 
-        @Override
-        public void binderDied() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    onConnectionDied(Connection.this);
-                }
-            });
-        }
-
         void openInputBridge(final IBinder token, final String name, final int width,
                              final int height, final int maxPointers) {
             synchronized (mLock) {
@@ -330,9 +263,9 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
-                                    name, width, height, maxPointers);
+                        if (mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
+                                                             name, width, height, maxPointers)) {
+                            onInputBridgeConnected(token);
                         }
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
@@ -356,9 +289,7 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
-                        }
+                        mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
@@ -381,9 +312,7 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
-                        }
+                        mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
@@ -412,10 +341,7 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token,
-                                    keyCode);
-                        }
+                        mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, keyCode);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
@@ -438,9 +364,7 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
-                        }
+                        mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
@@ -463,10 +387,8 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
-                                    pointerId, x, y);
-                        }
+                        mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
+                                pointerId, x, y);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
@@ -489,10 +411,8 @@
                     }
                     final long idToken = Binder.clearCallingIdentity();
                     try {
-                        if (mProviderMethods != null) {
-                            mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
-                                    pointerId);
-                        }
+                        mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
+                                pointerId);
                     } finally {
                         Binder.restoreCallingIdentity(idToken);
                     }
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
index d27970f..0d29edd 100644
--- a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java
@@ -45,7 +45,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
 
     private final Context mContext;
-    private final ProviderMethods mProvider;
+    private final TvRemoteProviderProxy.ProviderMethods mProvider;
     private final Handler mHandler;
     private final PackageManager mPackageManager;
     private final ArrayList<TvRemoteProviderProxy> mProviderProxies = new ArrayList<>();
@@ -54,10 +54,10 @@
 
     private boolean mRunning;
 
-    public TvRemoteProviderWatcher(Context context, ProviderMethods provider, Handler handler) {
+    TvRemoteProviderWatcher(Context context, TvRemoteProviderProxy.ProviderMethods provider) {
         mContext = context;
         mProvider = provider;
-        mHandler = handler;
+        mHandler = new Handler(true);
         mUserId = UserHandle.myUserId();
         mPackageManager = context.getPackageManager();
         mUnbundledServicePackage = context.getString(
@@ -116,12 +116,11 @@
                 int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
                 if (sourceIndex < 0) {
                     TvRemoteProviderProxy providerProxy =
-                            new TvRemoteProviderProxy(mContext,
+                            new TvRemoteProviderProxy(mContext, mProvider,
                                     new ComponentName(serviceInfo.packageName, serviceInfo.name),
                                     mUserId, serviceInfo.applicationInfo.uid);
                     providerProxy.start();
                     mProviderProxies.add(targetIndex++, providerProxy);
-                    mProvider.addProvider(providerProxy);
                 } else if (sourceIndex >= targetIndex) {
                     TvRemoteProviderProxy provider = mProviderProxies.get(sourceIndex);
                     provider.start(); // restart the provider if needed
@@ -135,7 +134,6 @@
         if (targetIndex < mProviderProxies.size()) {
             for (int i = mProviderProxies.size() - 1; i >= targetIndex; i--) {
                 TvRemoteProviderProxy providerProxy = mProviderProxies.get(i);
-                mProvider.removeProvider(providerProxy);
                 mProviderProxies.remove(providerProxy);
                 providerProxy.stop();
             }
@@ -212,10 +210,4 @@
             scanPackages();
         }
     };
-
-    public interface ProviderMethods {
-        void addProvider(TvRemoteProviderProxy providerProxy);
-
-        void removeProvider(TvRemoteProviderProxy providerProxy);
-    }
 }
diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java
index 4a41bf8..bee6fb3 100644
--- a/services/core/java/com/android/server/tv/TvRemoteService.java
+++ b/services/core/java/com/android/server/tv/TvRemoteService.java
@@ -17,10 +17,8 @@
 package com.android.server.tv;
 
 import android.content.Context;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -28,7 +26,6 @@
 import com.android.server.Watchdog;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Map;
 
 /**
@@ -43,9 +40,8 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_KEYS = false;
 
+    private final TvRemoteProviderWatcher mWatcher;
     private Map<IBinder, UinputBridge> mBridgeMap = new ArrayMap();
-    private Map<IBinder, TvRemoteProviderProxy> mProviderMap = new ArrayMap();
-    private ArrayList<TvRemoteProviderProxy> mProviderList = new ArrayList<>();
 
     /**
      * State guarded by mLock.
@@ -59,11 +55,10 @@
      */
     private final Object mLock = new Object();
 
-    public final UserHandler mHandler;
-
     public TvRemoteService(Context context) {
         super(context);
-        mHandler = new UserHandler(new UserProvider(TvRemoteService.this), context);
+        mWatcher = new TvRemoteProviderWatcher(context,
+                                               new UserProvider(TvRemoteService.this));
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -79,21 +74,17 @@
 
     @Override
     public void onBootPhase(int phase) {
+        // All lifecycle methods are called from the system server's main looper thread.
         if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             if (DEBUG) Slog.d(TAG, "PHASE_THIRD_PARTY_APPS_CAN_START");
-            mHandler.sendEmptyMessage(UserHandler.MSG_START);
+
+            mWatcher.start(); // Also schedules the start of all providers.
         }
     }
 
-    //Outgoing calls.
-    private void informInputBridgeConnected(IBinder token) {
-        mHandler.obtainMessage(UserHandler.MSG_INPUT_BRIDGE_CONNECTED, 0, 0, token).sendToTarget();
-    }
-
-    // Incoming calls.
-    private void openInputBridgeInternalLocked(TvRemoteProviderProxy provider, IBinder token,
-                                               String name, int width, int height,
-                                               int maxPointers) {
+    private boolean openInputBridgeInternalLocked(final IBinder token,
+                                                  String name, int width, int height,
+                                                  int maxPointers) {
         if (DEBUG) {
             Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name +
                     ", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers);
@@ -103,22 +94,31 @@
             //Create a new bridge, if one does not exist already
             if (mBridgeMap.containsKey(token)) {
                 if (DEBUG) Slog.d(TAG, "RemoteBridge already exists");
-                // Respond back with success.
-                informInputBridgeConnected(token);
-                return;
+                return true;
             }
 
             UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers);
-
             mBridgeMap.put(token, inputBridge);
-            mProviderMap.put(token, provider);
 
-            // Respond back with success.
-            informInputBridgeConnected(token);
-
+            try {
+                token.linkToDeath(new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        synchronized (mLock) {
+                            closeInputBridgeInternalLocked(token);
+                        }
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) Slog.d(TAG, "Token is already dead");
+                closeInputBridgeInternalLocked(token);
+                return false;
+            }
         } catch (IOException ioe) {
             Slog.e(TAG, "Cannot create device for " + name);
+            return false;
         }
+        return true;
     }
 
     private void closeInputBridgeInternalLocked(IBinder token) {
@@ -135,7 +135,6 @@
         mBridgeMap.remove(token);
     }
 
-
     private void clearInputBridgeInternalLocked(IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "clearInputBridgeInternalLocked(), token: " + token);
@@ -204,47 +203,7 @@
         }
     }
 
-    private final class UserHandler extends Handler {
-
-        public static final int MSG_START = 1;
-        public static final int MSG_INPUT_BRIDGE_CONNECTED = 2;
-
-        private final TvRemoteProviderWatcher mWatcher;
-        private boolean mRunning;
-
-        public UserHandler(UserProvider provider, Context context) {
-            super(Looper.getMainLooper(), null, true);
-            mWatcher = new TvRemoteProviderWatcher(context, provider, this);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START: {
-                    start();
-                    break;
-                }
-                case MSG_INPUT_BRIDGE_CONNECTED: {
-                    IBinder token = (IBinder) msg.obj;
-                    TvRemoteProviderProxy provider = mProviderMap.get(token);
-                    if (provider != null) {
-                        provider.inputBridgeConnected(token);
-                    }
-                    break;
-                }
-            }
-        }
-
-        private void start() {
-            if (!mRunning) {
-                mRunning = true;
-                mWatcher.start(); // also starts all providers
-            }
-        }
-    }
-
-    private final class UserProvider implements TvRemoteProviderWatcher.ProviderMethods,
-            TvRemoteProviderProxy.ProviderMethods {
+    private final class UserProvider implements TvRemoteProviderProxy.ProviderMethods {
 
         private final TvRemoteService mService;
 
@@ -253,8 +212,8 @@
         }
 
         @Override
-        public void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
-                                    int width, int height, int maxPointers) {
+        public boolean openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
+                                       int width, int height, int maxPointers) {
             if (DEBUG) {
                 Slog.d(TAG, "openInputBridge(), token: " + token +
                         ", name: " + name + ", width: " + width +
@@ -262,10 +221,8 @@
             }
 
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
-                    mService.openInputBridgeInternalLocked(provider, token, name, width, height,
-                            maxPointers);
-                }
+                return mService.openInputBridgeInternalLocked(token, name, width,
+                               height, maxPointers);
             }
         }
 
@@ -273,10 +230,7 @@
         public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) {
             if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token);
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.closeInputBridgeInternalLocked(token);
-                    mProviderMap.remove(token);
-                }
             }
         }
 
@@ -284,9 +238,7 @@
         public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) {
             if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token);
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.clearInputBridgeInternalLocked(token);
-                }
             }
         }
 
@@ -296,9 +248,7 @@
                 Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode);
             }
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.sendKeyDownInternalLocked(token, keyCode);
-                }
             }
         }
 
@@ -308,9 +258,7 @@
                 Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode);
             }
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.sendKeyUpInternalLocked(token, keyCode);
-                }
             }
         }
 
@@ -321,9 +269,7 @@
                 Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId);
             }
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.sendPointerDownInternalLocked(token, pointerId, x, y);
-                }
             }
         }
 
@@ -333,9 +279,7 @@
                 Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId);
             }
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.sendPointerUpInternalLocked(token, pointerId);
-                }
             }
         }
 
@@ -343,29 +287,7 @@
         public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) {
             if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token);
             synchronized (mLock) {
-                if (mProviderList.contains(provider)) {
                     mService.sendPointerSyncInternalLocked(token);
-                }
-            }
-        }
-
-        @Override
-        public void addProvider(TvRemoteProviderProxy provider) {
-            if (DEBUG) Slog.d(TAG, "addProvider " + provider);
-            synchronized (mLock) {
-                provider.setProviderSink(this);
-                mProviderList.add(provider);
-                Slog.d(TAG, "provider: " + provider.toString());
-            }
-        }
-
-        @Override
-        public void removeProvider(TvRemoteProviderProxy provider) {
-            if (DEBUG) Slog.d(TAG, "removeProvider " + provider);
-            synchronized (mLock) {
-                if (mProviderList.remove(provider) == false) {
-                    Slog.e(TAG, "Unknown provider " + provider);
-                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/utils/TraceBuffer.java b/services/core/java/com/android/server/utils/TraceBuffer.java
new file mode 100644
index 0000000..0567960
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TraceBuffer.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
+
+/**
+ * Buffer used for tracing and logging.
+ */
+public class TraceBuffer {
+    private final Object mBufferLock = new Object();
+
+    private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
+    private int mBufferUsedSize;
+    private int mBufferCapacity;
+
+    public TraceBuffer(int bufferCapacity) {
+        mBufferCapacity = bufferCapacity;
+        resetBuffer();
+    }
+
+    public int getAvailableSpace() {
+        return mBufferCapacity - mBufferUsedSize;
+    }
+
+    /**
+     * Returns buffer size.
+     */
+    public int size() {
+        return mBuffer.size();
+    }
+
+    public void setCapacity(int capacity) {
+        mBufferCapacity = capacity;
+    }
+
+    /**
+     * Inserts the specified element into this buffer.
+     *
+     * @param proto the element to add
+     * @throws IllegalStateException if the element cannot be added because it is larger
+     *                               than the buffer size.
+     */
+    public void add(ProtoOutputStream proto) {
+        int protoLength = proto.getRawSize();
+        if (protoLength > mBufferCapacity) {
+            throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
+                    + mBufferCapacity + " Object size: " + protoLength);
+        }
+        synchronized (mBufferLock) {
+            discardOldest(protoLength);
+            mBuffer.add(proto);
+            mBufferUsedSize += protoLength;
+            mBufferLock.notify();
+        }
+    }
+
+    boolean contains(byte[] other) {
+        return mBuffer.stream()
+                .anyMatch(p -> Arrays.equals(p.getBytes(), other));
+    }
+
+    /**
+     * Writes the trace buffer to disk inside the encapsulatingProto..
+     */
+    public void writeTraceToFile(File traceFile, ProtoOutputStream encapsulatingProto)
+            throws IOException {
+        synchronized (mBufferLock) {
+            traceFile.delete();
+            try (OutputStream os = new FileOutputStream(traceFile)) {
+                traceFile.setReadable(true /* readable */, false /* ownerOnly */);
+                os.write(encapsulatingProto.getBytes());
+                for (ProtoOutputStream protoOutputStream : mBuffer) {
+                    encapsulatingProto = protoOutputStream;
+                    byte[] protoBytes = encapsulatingProto.getBytes();
+                    os.write(protoBytes);
+                }
+                os.flush();
+            }
+        }
+    }
+
+    /**
+     * Checks if the element can be added to the buffer. The element is already certain to be
+     * smaller than the overall buffer size.
+     *
+     * @param protoLength byte array representation of the Proto object to add
+     */
+    private void discardOldest(int protoLength) {
+        long availableSpace = getAvailableSpace();
+
+        while (availableSpace < protoLength) {
+
+            ProtoOutputStream item = mBuffer.poll();
+            if (item == null) {
+                throw new IllegalStateException("No element to discard from buffer");
+            }
+            mBufferUsedSize -= item.getRawSize();
+            availableSpace = getAvailableSpace();
+        }
+    }
+
+    /**
+     * Removes all elements form the buffer
+     */
+    public void resetBuffer() {
+        synchronized (mBufferLock) {
+            mBuffer.clear();
+            mBufferUsedSize = 0;
+        }
+    }
+
+    @VisibleForTesting
+    int getBufferSize() {
+        return mBufferUsedSize;
+    }
+
+    /**
+     * Returns the buffer status in human-readable form.
+     */
+    public String getStatus() {
+        synchronized (mBufferLock) {
+            return "Buffer size: "
+                    + mBufferCapacity
+                    + " bytes"
+                    + "\n"
+                    + "Buffer usage: "
+                    + mBufferUsedSize
+                    + " bytes"
+                    + "\n"
+                    + "Elements in the buffer: "
+                    + mBuffer.size();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b0f1e5d..3cdb59b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -37,6 +37,7 @@
 import android.app.WallpaperColors;
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
+import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.BroadcastReceiver;
@@ -73,7 +74,6 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -91,9 +91,9 @@
 import android.util.Xml;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.IWindowManager;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
@@ -739,7 +739,6 @@
     }
 
     private final Context mContext;
-    private final IWindowManager mIWindowManager;
     private final WindowManagerInternal mWindowManagerInternal;
     private final IPackageManager mIPackageManager;
     private final MyPackageMonitor mMonitor;
@@ -792,7 +791,7 @@
      */
     private final SparseArray<SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>>
             mColorsChangedListeners;
-    private WallpaperData mLastWallpaper;
+    protected WallpaperData mLastWallpaper;
     private IWallpaperManagerCallback mKeyguardListener;
     private boolean mWaitingForUnlock;
     private boolean mShuttingDown;
@@ -825,7 +824,7 @@
 
     private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
 
-    private WallpaperData mFallbackWallpaper;
+    protected WallpaperData mFallbackWallpaper;
 
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -900,9 +899,8 @@
          */
         final Rect cropHint = new Rect(0, 0, 0, 0);
 
-        WallpaperData(int userId, String inputFileName, String cropFileName) {
+        WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) {
             this.userId = userId;
-            final File wallpaperDir = getWallpaperDir(userId);
             wallpaperFile = new File(wallpaperDir, inputFileName);
             cropFile = new File(wallpaperDir, cropFileName);
         }
@@ -917,7 +915,8 @@
         }
     }
 
-    private static final class DisplayData {
+    @VisibleForTesting
+    static final class DisplayData {
         int mWidth = -1;
         int mHeight = -1;
         final Rect mPadding = new Rect(0, 0, 0, 0);
@@ -1057,13 +1056,7 @@
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
-                try {
-                    mIWindowManager.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed add wallpaper window token on display " + mDisplayId, e);
-                    return;
-                }
-
+                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
                 final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
@@ -1081,10 +1074,8 @@
 
             void disconnectLocked() {
                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
-                try {
-                    mIWindowManager.removeWindowToken(mToken, mDisplayId);
-                } catch (RemoteException e) {
-                }
+                mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
+                        mDisplayId);
                 try {
                     if (mEngine != null) {
                         mEngine.destroy();
@@ -1562,6 +1553,15 @@
         }
     }
 
+    @VisibleForTesting
+    WallpaperData getCurrentWallpaperData(@SetWallpaperFlags int which, int userId) {
+        synchronized (mLock) {
+            final SparseArray<WallpaperData> wallpaperDataMap =
+                    which == FLAG_SYSTEM ? mWallpaperMap : mLockWallpaperMap;
+            return wallpaperDataMap.get(userId);
+        }
+    }
+
     public WallpaperManagerService(Context context) {
         if (DEBUG) Slog.v(TAG, "WallpaperService startup");
         mContext = context;
@@ -1569,8 +1569,6 @@
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
         mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
-        mIWindowManager = IWindowManager.Stub.asInterface(
-                ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1600,7 +1598,7 @@
         getWallpaperSafeLocked(UserHandle.USER_SYSTEM, FLAG_SYSTEM);
     }
 
-    private static File getWallpaperDir(int userId) {
+    File getWallpaperDir(int userId) {
         return Environment.getUserSystemDirectory(userId);
     }
 
@@ -1819,7 +1817,8 @@
                     // while locked, so pretend like the component was actually
                     // bound into place
                     wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
-                    final WallpaperData fallback = new WallpaperData(wallpaper.userId,
+                    final WallpaperData fallback =
+                            new WallpaperData(wallpaper.userId, getWallpaperDir(wallpaper.userId),
                             WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                     ensureSaneWallpaperData(fallback, DEFAULT_DISPLAY);
                     bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
@@ -2380,7 +2379,7 @@
         }
 
         // We know a-priori that there is no lock-only wallpaper currently
-        WallpaperData lockWP = new WallpaperData(userId,
+        WallpaperData lockWP = new WallpaperData(userId, getWallpaperDir(userId),
                 WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
         lockWP.wallpaperId = sysWP.wallpaperId;
         lockWP.cropHint.set(sysWP.cropHint);
@@ -2793,7 +2792,7 @@
         }
     }
 
-    private static JournaledFile makeJournaledFile(int userId) {
+    private JournaledFile makeJournaledFile(int userId) {
         final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
@@ -2958,7 +2957,7 @@
             // it now.
             if (wallpaper == null) {
                 if (which == FLAG_LOCK) {
-                    wallpaper = new WallpaperData(userId,
+                    wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
                             WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                     mLockWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY);
@@ -2966,7 +2965,8 @@
                     // sanity fallback: we're in bad shape, but establishing a known
                     // valid system+lock WallpaperData will keep us from dying.
                     Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
-                    wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
+                    wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
+                            WALLPAPER, WALLPAPER_CROP);
                     mWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY);
                 }
@@ -2985,7 +2985,8 @@
             // Do this once per boot
             migrateFromOld();
 
-            wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
+            wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
+                    WALLPAPER, WALLPAPER_CROP);
             wallpaper.allowBackup = true;
             mWallpaperMap.put(userId, wallpaper);
             if (!wallpaper.cropExists()) {
@@ -3037,7 +3038,7 @@
                         // keyguard-specific wallpaper for this user
                         WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
                         if (lockWallpaper == null) {
-                            lockWallpaper = new WallpaperData(userId,
+                            lockWallpaper = new WallpaperData(userId, getWallpaperDir(userId),
                                     WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                             mLockWallpaperMap.put(userId, lockWallpaper);
                         }
@@ -3088,8 +3089,9 @@
     private void initializeFallbackWallpaper() {
         if (mFallbackWallpaper == null) {
             if (DEBUG) Slog.d(TAG, "Initialize fallback wallpaper");
-            mFallbackWallpaper = new WallpaperData(
-                    UserHandle.USER_SYSTEM, WALLPAPER, WALLPAPER_CROP);
+            final int systemUserId = UserHandle.USER_SYSTEM;
+            mFallbackWallpaper = new WallpaperData(systemUserId, getWallpaperDir(systemUserId),
+                    WALLPAPER, WALLPAPER_CROP);
             mFallbackWallpaper.allowBackup = false;
             mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked();
             bindWallpaperComponentLocked(mImageWallpaper, true, false, mFallbackWallpaper, null);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdater.java b/services/core/java/com/android/server/webkit/WebViewUpdater.java
index a460040..3b58af2 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdater.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdater.java
@@ -87,19 +87,6 @@
                         newPackage = findPreferredWebViewPackage();
                         if (mCurrentWebViewPackage != null) {
                             oldProviderName = mCurrentWebViewPackage.packageName;
-                            if (changedState == WebViewUpdateService.PACKAGE_CHANGED
-                                    && newPackage.packageName.equals(oldProviderName)) {
-                                // If we don't change package name we should only rerun the
-                                // preparation phase if the current package has been replaced
-                                // (not if it has been enabled/disabled).
-                                return;
-                            }
-                            if (newPackage.packageName.equals(oldProviderName)
-                                    && (newPackage.lastUpdateTime
-                                        == mCurrentWebViewPackage.lastUpdateTime)) {
-                                // If the chosen package hasn't been updated, then early-out
-                                return;
-                            }
                         }
                         // Only trigger update actions if the updated package is the one
                         // that will be used, or the one that was in use before the
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 30a3aef..59f051b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -255,13 +255,19 @@
         }
     }
 
-    public void onSomeWindowResizedOrMovedLocked(int displayId) {
+    /**
+     * Called when the location or the size of the window is changed. Moving the window to
+     * another display is also taken into consideration.
+     * @param displayIds the display ids of displays when the situation happens.
+     */
+    public void onSomeWindowResizedOrMovedLocked(int... displayIds) {
         // Not relevant for the display magnifier.
-
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(displayId);
-        if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+        for (int i = 0; i < displayIds.length; i++) {
+            final WindowsForAccessibilityObserver windowsForA11yObserver =
+                    mWindowsForAccessibilityObserver.get(displayIds[i]);
+            if (windowsForA11yObserver != null) {
+                windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            }
         }
     }
 
@@ -808,7 +814,7 @@
                 private final Paint mPaint = new Paint();
 
                 private final SurfaceControl mSurfaceControl;
-                private final Surface mSurface = new Surface();
+                private final Surface mSurface = mService.mSurfaceFactory.get();
 
                 private final AnimationController mAnimationController;
 
@@ -961,7 +967,7 @@
                 }
 
                 public void releaseSurface() {
-                    mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
+                    mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
                     mSurface.release();
                 }
 
@@ -1100,14 +1106,10 @@
 
         private final Point mTempPoint = new Point();
 
-        private final Rect mTempRect = new Rect();
-
         private final Region mTempRegion = new Region();
 
         private final Region mTempRegion1 = new Region();
 
-        private final Context mContext;
-
         private final WindowManagerService mService;
 
         private final Handler mHandler;
@@ -1121,7 +1123,6 @@
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 int displayId,
                 WindowsForAccessibilityCallback callback) {
-            mContext = windowManagerService.mContext;
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
@@ -1240,6 +1241,11 @@
         private boolean windowMattersToAccessibility(WindowState windowState,
                 Region regionInScreen, Region unaccountedSpace,
                 HashSet<Integer> skipRemainingWindowsForTasks) {
+            final RecentsAnimationController controller = mService.getRecentsAnimationController();
+            if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
+                return false;
+            }
+
             if (windowState.isFocused()) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e76078f..e488cc9 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -574,11 +574,9 @@
      * then we should explicitly pause that stack's top activity.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
      * @param resuming The resuming activity.
-     * @param dontWait The resuming activity isn't going to wait for all activities to be paused
-     *                 before resuming.
      * @return {@code true} if any activity was paused as a result of this call.
      */
-    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
+    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
         boolean someActivityPaused = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
@@ -588,8 +586,8 @@
                         || !stack.isFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                         " mResumedActivity=" + resumedActivity);
-                someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
-                        dontWait);
+                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
+                        resuming);
             }
         }
         return someActivityPaused;
@@ -742,7 +740,7 @@
     }
 
     private void onSplitScreenModeDismissed() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -766,12 +764,12 @@
                 mHomeStack.moveToFront("onSplitScreenModeDismissed");
                 topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
             }
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
     private void onSplitScreenModeActivated() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -786,7 +784,7 @@
                         false /* creating */);
             }
         } finally {
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1004,12 +1002,9 @@
         Configuration values = new Configuration();
         mDisplayContent.computeScreenConfiguration(values);
 
-        if (mService.mWindowManager != null) {
-            final Message msg = PooledLambda.obtainMessage(
-                    ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
-                    mDisplayId);
-            mService.mH.sendMessage(msg);
-        }
+        mService.mH.sendMessage(PooledLambda.obtainMessage(
+                ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
+                mDisplayId));
 
         Settings.System.clearConfiguration(values);
         updateDisplayOverrideConfigurationLocked(values, null /* starting */,
@@ -1028,9 +1023,7 @@
         int changes = 0;
         boolean kept = true;
 
-        if (mService.mWindowManager != null) {
-            mService.mWindowManager.deferSurfaceLayout();
-        }
+        mService.deferWindowLayout();
         try {
             if (values != null) {
                 if (mDisplayId == DEFAULT_DISPLAY) {
@@ -1047,9 +1040,7 @@
 
             kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mService.mWindowManager != null) {
-                mService.mWindowManager.continueSurfaceLayout();
-            }
+            mService.continueWindowLayout();
         }
 
         if (result != null) {
@@ -1098,6 +1089,8 @@
             mService.mWindowManager.setNewDisplayOverrideConfiguration(
                     overrideConfiguration, mDisplayContent);
         }
+        mService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fc36e99..c54ccd4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -118,7 +118,6 @@
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
@@ -1664,11 +1663,10 @@
     @interface FinishRequest {}
 
     /**
-     * See {@link #finishIfPossible(int, Intent, String, boolean, boolean)}
+     * See {@link #finishIfPossible(int, Intent, String, boolean)}
      */
     @FinishRequest int finishIfPossible(String reason, boolean oomAdj) {
-        return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
-                oomAdj, !PAUSE_IMMEDIATELY);
+        return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason, oomAdj);
     }
 
     /**
@@ -1683,7 +1681,7 @@
      * request to finish it was not ignored.
      */
     @FinishRequest int finishIfPossible(int resultCode, Intent resultData, String reason,
-            boolean oomAdj, boolean pauseImmediately) {
+            boolean oomAdj) {
         if (DEBUG_RESULTS || DEBUG_STATES) {
             Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode
                     + ", data=" + resultData + ", reason=" + reason);
@@ -1699,7 +1697,13 @@
             return FINISH_RESULT_CANCELLED;
         }
 
-        mAtmService.mWindowManager.deferSurfaceLayout();
+        final ActivityStack stack = getActivityStack();
+        final boolean mayAdjustFocus = (isState(RESUMED) || stack.mResumedActivity == null)
+                // It must be checked before {@link #makeFinishingLocked} is called, because a stack
+                // is not visible if it only contains finishing activities.
+                && mRootActivityContainer.isTopDisplayFocusedStack(stack);
+
+        mAtmService.deferWindowLayout();
         try {
             makeFinishingLocked();
             final TaskRecord task = getTaskRecord();
@@ -1720,8 +1724,12 @@
 
             pauseKeyDispatchingLocked();
 
-            final ActivityStack stack = getActivityStack();
-            stack.adjustFocusedActivityStack(this, "finishIfPossible");
+            // We are finishing the top focused activity and its stack has nothing to be focused so
+            // the next focusable stack should be focused.
+            if (mayAdjustFocus
+                    && (stack.topRunningActivityLocked() == null || !stack.isFocusable())) {
+                stack.adjustFocusToNextFocusableStack("finish-top");
+            }
 
             finishActivityResults(resultCode, resultData);
 
@@ -1758,7 +1766,8 @@
                     if (DEBUG_USER_LEAVING) {
                         Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
                     }
-                    stack.startPausingLocked(false, false, null, pauseImmediately);
+                    stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+                            null /* resuming */);
                 }
 
                 if (endTask) {
@@ -1800,7 +1809,7 @@
 
             return FINISH_RESULT_REQUESTED;
         } finally {
-            mAtmService.mWindowManager.continueSurfaceLayout();
+            mAtmService.continueWindowLayout();
         }
     }
 
@@ -1844,9 +1853,14 @@
         final ActivityRecord next = getDisplay().topRunningActivity(
                 true /* considerKeyguardState */);
         final boolean isVisible = visible || nowVisible;
+        // isNextNotYetVisible is to check if the next activity is invisible, or it has been
+        // requested to be invisible but its windows haven't reported as invisible.  If so, it
+        // implied that the current finishing activity should be added into stopping list rather
+        // than destroy immediately.
+        final boolean isNextNotYetVisible = next != null && (!next.nowVisible || !next.visible);
         final ActivityStack stack = getActivityStack();
         final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack();
-        if (isVisible && next != null && !next.nowVisible) {
+        if (isVisible && isNextNotYetVisible) {
             addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
                     "completeFinishing");
             if (DEBUG_STATES) {
@@ -1935,7 +1949,10 @@
      * HistoryRecord.
      * Normally the server-side record will be removed when the client reports back after
      * destruction. If, however, at this point there is no client process attached, the record will
-     * removed immediately.
+     * be removed immediately.
+     *
+     * @return {@code true} if activity was immediately removed from history, {@code false}
+     * otherwise.
      */
     boolean destroyImmediately(boolean removeFromApp, String reason) {
         if (DEBUG_SWITCH || DEBUG_CLEANUP) {
@@ -2091,7 +2108,7 @@
                 // TODO: If the callers to removeTask() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeTask()
                 mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
+                        !REMOVE_FROM_RECENTS, reason);
             }
 
             // We must keep the task around until all activities are destroyed. The following
@@ -2185,7 +2202,9 @@
         stack.removeTimeoutsForActivity(this);
         // Clean-up activities are no longer relaunching (e.g. app process died). Notify window
         // manager so it can update its bookkeeping.
-        mAtmService.mWindowManager.notifyAppRelaunchesCleared(appToken);
+        if (mAppWindowToken != null) {
+            mAppWindowToken.clearRelaunching();
+        }
     }
 
     /**
@@ -2530,6 +2549,8 @@
             return;
         }
         mAppWindowToken.setVisibility(visible, mDeferHidingClient);
+        mAtmService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
         mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
     }
 
@@ -2918,7 +2939,7 @@
         return false;
     }
 
-    boolean handleAlreadyVisible() {
+    void handleAlreadyVisible() {
         stopFreezingScreenLocked(false);
         try {
             if (returningOptions != null) {
@@ -2926,7 +2947,6 @@
             }
         } catch(RemoteException e) {
         }
-        return mState == RESUMED;
     }
 
     static void activityResumedLocked(IBinder token) {
@@ -2943,6 +2963,11 @@
         if (display != null) {
             display.handleActivitySizeCompatModeIfNeeded(r);
         }
+
+        if (r.mAppWindowToken != null) {
+            r.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController
+                    .notifyAppResumedFinished(r.mAppWindowToken);
+        }
     }
 
     /**
@@ -3010,7 +3035,6 @@
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this);
                     if (finishIfPossible("stop-no-history", false /* oomAdj */)
                             != FINISH_RESULT_CANCELLED) {
-                        // {@link adjustFocusedActivityStack} must have been already called.
                         resumeKeyDispatchingLocked();
                         return;
                     }
@@ -3026,7 +3050,6 @@
         if (!attachedToProcess()) {
             return;
         }
-        stack.adjustFocusedActivityStack(this, "stopActivity");
         resumeKeyDispatchingLocked();
         try {
             stopped = false;
@@ -3430,8 +3453,8 @@
             return false;
         }
         final ActivityStack stack = getActivityStack();
-        if (stack == null || this == stack.getResumedActivity() || this == stack.mPausingActivity
-                || !mHaveState || !stopped) {
+        if (isState(RESUMED) || stack == null || this == stack.mPausingActivity || !mHaveState
+                || !stopped) {
             // We're not ready for this kind of thing.
             return false;
         }
@@ -4295,7 +4318,9 @@
                     "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + this
                             + " callers=" + Debug.getCallers(6));
             forceNewConfig = false;
-            mStackSupervisor.activityRelaunchingLocked(this);
+            if (mAppWindowToken != null) {
+                mAppWindowToken.startRelaunching();
+            }
             final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
                     pendingNewIntents, configChangeFlags,
                     new MergedConfiguration(mAtmService.getGlobalConfiguration(),
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 8bdedff..2ab3e01 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -33,6 +33,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.view.Display.INVALID_DISPLAY;
@@ -63,7 +64,6 @@
 import static com.android.server.wm.ActivityStack.ActivityState.STARTED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
@@ -135,6 +135,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
@@ -636,11 +637,15 @@
             display.onStackWindowingModeChanged(this);
         }
         if (hasNewOverrideBounds) {
-            // Note the resizeStack may enter onConfigurationChanged recursively, so we make a copy
-            // of the temporary bounds (newBounds is mTmpRect) to avoid it being modified.
-            mRootActivityContainer.resizeStack(this, new Rect(newBounds), null /* tempTaskBounds */,
-                    null /* tempTaskInsetBounds */, PRESERVE_WINDOWS,
-                    true /* allowResizeInDockedMode */, true /* deferResume */);
+            if (inSplitScreenPrimaryWindowingMode()) {
+                mStackSupervisor.resizeDockedStackLocked(new Rect(newBounds),
+                        null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
+                        PRESERVE_WINDOWS, true /* deferResume */);
+            } else {
+                resize(new Rect(newBounds), null /* tempTaskBounds */,
+                        null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
+            }
         }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
             // Since always on top is only on when the stack is freeform or pinned, the state
@@ -752,7 +757,6 @@
             return;
         }
 
-        final WindowManagerService wm = mService.mWindowManager;
         final ActivityRecord topActivity = getTopActivity();
 
         // For now, assume that the Stack's windowing mode is what will actually be used
@@ -774,7 +778,7 @@
                     topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
         }
 
-        wm.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (!animate && topActivity != null) {
                 mStackSupervisor.mNoAnimActivities.add(topActivity);
@@ -819,7 +823,8 @@
             }
 
             if (!Objects.equals(getRequestedOverrideBounds(), mTmpRect2)) {
-                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        false /* preserveWindows */, true /* deferResume */);
             }
         } finally {
             if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
@@ -834,17 +839,18 @@
                 // so that the divider matches and remove this logic.
                 // TODO: This is currently only called when entering split-screen while in another
                 // task, and from the tests
-                // TODO (b/78247419): Check if launcher and overview are same then move home stack
-                // instead of recents stack. Then fix the rotation animation from fullscreen to
-                // minimized mode
+                // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode
+                final boolean isRecentsComponentHome =
+                        mService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser);
                 final ActivityStack recentStack = display.getOrCreateStack(
-                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS,
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS,
                         true /* onTop */);
                 recentStack.moveToFront("setWindowingMode");
                 // If task moved to docked stack - show recents if needed.
                 mService.mWindowManager.showRecentApps();
             }
-            wm.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (!deferEnsuringVisibility) {
@@ -1547,7 +1553,7 @@
             if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
                     "Sleep => pause with userLeaving=false");
 
-            startPausingLocked(false, true, null, false);
+            startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */);
             shouldSleep = false ;
         } else if (mPausingActivity != null) {
             // Still waiting for something to pause; can't sleep yet.
@@ -1617,13 +1623,11 @@
      * @param resuming The activity we are currently trying to resume or null if this is not being
      *                 called as part of resuming the top activity, so we shouldn't try to instigate
      *                 a resume here if not null.
-     * @param pauseImmediately True if the caller does not want to wait for the activity callback to
-     *                         complete pausing.
      * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
      * it to tell us when it is done.
      */
     final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
-            ActivityRecord resuming, boolean pauseImmediately) {
+            ActivityRecord resuming) {
         if (mPausingActivity != null) {
             Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
                     + " state=" + mPausingActivity.getState());
@@ -1661,6 +1665,19 @@
 
         mService.updateCpuStats();
 
+        boolean pauseImmediately = false;
+        if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
+            // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
+            // activity to be paused, while at the same time resuming the new resume activity
+            // only if the previous activity can't go into Pip since we want to give Pip
+            // activities a chance to enter Pip before resuming the next activity.
+            final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
+                    "shouldResumeWhilePausing", userLeaving);
+            if (!lastResumedCanPip) {
+                pauseImmediately = true;
+            }
+        }
+
         if (prev.attachedToProcess()) {
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
             try {
@@ -1733,11 +1750,11 @@
             if (mPausingActivity == r) {
                 if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r
                         + (timeout ? " (due to timeout)" : " (pause complete)"));
-                mService.mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
                 } finally {
-                    mService.mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                 }
                 return;
             } else {
@@ -1758,7 +1775,8 @@
         mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
-    private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
+    @VisibleForTesting
+    void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
         ActivityRecord prev = mPausingActivity;
         if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
 
@@ -2088,15 +2106,12 @@
             boolean aboveTop = top != null;
             final boolean stackShouldBeVisible = shouldBeVisible(starting);
             boolean behindFullscreenActivity = !stackShouldBeVisible;
-            boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
+            final boolean resumeTopActivity = isFocusable() && isInStackLocked(starting) == null;
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
                 final ArrayList<ActivityRecord> activities = task.mActivities;
                 for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                     final ActivityRecord r = activities.get(activityNdx);
-                    if (r.finishing) {
-                        continue;
-                    }
                     final boolean isTop = r == top;
                     if (aboveTop && !isTop) {
                         continue;
@@ -2112,6 +2127,9 @@
                                 behindFullscreenActivity, r);
                     }
                     if (reallyVisible) {
+                        if (r.finishing) {
+                            continue;
+                        }
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
                                 + " finishing=" + r.finishing + " state=" + r.getState());
                         // First: if this is not the current activity being started, make
@@ -2122,15 +2140,8 @@
                         }
 
                         if (!r.attachedToProcess()) {
-                            if (makeVisibleAndRestartIfNeeded(starting, configChanges, isTop,
-                                    resumeNextActivity, r)) {
-                                if (activityNdx >= activities.size()) {
-                                    // Record may be removed if its process needs to restart.
-                                    activityNdx = activities.size() - 1;
-                                } else {
-                                    resumeNextActivity = false;
-                                }
-                            }
+                            makeVisibleAndRestartIfNeeded(starting, configChanges, isTop,
+                                    resumeTopActivity && isTop, r);
                         } else if (r.visible) {
                             // If this activity is already visible, then there is nothing to do here.
                             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
@@ -2140,10 +2151,7 @@
                                 r.makeClientVisible();
                             }
 
-                            if (r.handleAlreadyVisible()) {
-                                resumeNextActivity = false;
-                            }
-
+                            r.handleAlreadyVisible();
                             if (notifyClients) {
                                 r.makeActiveIfNeeded(starting);
                             }
@@ -2207,7 +2215,7 @@
      * Returns true if this stack should be resized to match the bounds specified by
      * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
      */
-    boolean resizeStackWithLaunchBounds() {
+    boolean shouldResizeStackWithLaunchBounds() {
         return inPinnedWindowingMode();
     }
 
@@ -2604,7 +2612,6 @@
 
         mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
 
-        boolean lastResumedCanPip = false;
         ActivityRecord lastResumed = null;
         final ActivityStack lastFocusedStack = display.getLastFocusedStack();
         if (lastFocusedStack != null && lastFocusedStack != this) {
@@ -2618,23 +2625,15 @@
                         + " next=" + next + " lastResumed=" + lastResumed);
                 userLeaving = false;
             }
-            lastResumedCanPip = lastResumed != null && lastResumed.checkEnterPictureInPictureState(
-                    "resumeTopActivity", userLeaving /* beforeStopping */);
         }
-        // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
-        // to be paused, while at the same time resuming the new resume activity only if the
-        // previous activity can't go into Pip since we want to give Pip activities a chance to
-        // enter Pip before resuming the next activity.
-        final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0
-                && !lastResumedCanPip;
 
-        boolean pausing = display.pauseBackStacks(userLeaving, next, false);
+        boolean pausing = display.pauseBackStacks(userLeaving, next);
         if (mResumedActivity != null) {
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "resumeTopActivityLocked: Pausing " + mResumedActivity);
-            pausing |= startPausingLocked(userLeaving, false, next, false);
+            pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next);
         }
-        if (pausing && !resumeWhilePausing) {
+        if (pausing) {
             if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
                     "resumeTopActivityLocked: Skip resume: need to start pausing");
             // At this point we want to put the upcoming activity's process
@@ -3552,50 +3551,6 @@
         return taskTop;
     }
 
-    void adjustFocusedActivityStack(ActivityRecord r, String reason) {
-        if (!mRootActivityContainer.isTopDisplayFocusedStack(this) ||
-                ((mResumedActivity != r) && (mResumedActivity != null))) {
-            return;
-        }
-
-        final ActivityRecord next = topRunningActivityLocked();
-        final String myReason = reason + " adjustFocus";
-
-        if (next == r) {
-            final ActivityRecord top = mRootActivityContainer.topRunningActivity();
-            if (top != null) {
-                top.moveFocusableActivityToTop(myReason);
-            }
-            return;
-        }
-
-        if (next != null && isFocusable()) {
-            // Keep focus in stack if we have a top running activity and are focusable.
-            return;
-        }
-
-        // Task is not guaranteed to be non-null. For example, destroying the
-        // {@link ActivityRecord} will disassociate the task from the activity.
-        final TaskRecord task = r.getTaskRecord();
-
-        if (task == null) {
-            throw new IllegalStateException("activity no longer associated with task:" + r);
-        }
-
-        // Move focus to next focusable stack if possible.
-        final ActivityStack nextFocusableStack = adjustFocusToNextFocusableStack(myReason);
-        if (nextFocusableStack != null) {
-            final ActivityRecord top = nextFocusableStack.topRunningActivityLocked();
-            if (top != null && top == mRootActivityContainer.getTopResumedActivity()) {
-                mService.setResumedActivityUncheckLocked(top, reason);
-            }
-            return;
-        }
-
-        // Whatever...go home.
-        getDisplay().moveHomeActivityToTop(myReason);
-    }
-
     /**
      * Find next proper focusable stack and make it focused.
      * @return The stack that now got the focus, {@code null} if none found.
@@ -3627,6 +3582,14 @@
         }
 
         stack.moveToFront(myReason);
+        // Top display focused stack is changed, update top resumed activity if needed.
+        if (stack.mResumedActivity != null) {
+            mStackSupervisor.updateTopResumedActivityIfNeeded();
+            // Set focused app directly because if the next focused activity is already resumed
+            // (e.g. the next top activity is on a different display), there won't have activity
+            // state change to update it.
+            mService.setResumedActivityUncheckLocked(stack.mResumedActivity, reason);
+        }
         return stack;
     }
 
@@ -3848,8 +3811,7 @@
         final long origId = Binder.clearCallingIdentity();
         for (int i = start; i > finishTo; i--) {
             final ActivityRecord r = activities.get(i);
-            r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */,
-                    !PAUSE_IMMEDIATELY);
+            r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */);
             // Only return the supplied result for the first activity finished
             resultCode = Activity.RESULT_CANCELED;
             resultData = null;
@@ -3886,8 +3848,7 @@
                 } catch (RemoteException e) {
                     foundParentInTask = false;
                 }
-                parent.finishIfPossible(resultCode, resultData, "navigate-top",
-                        true /* oomAdj */, !PAUSE_IMMEDIATELY);
+                parent.finishIfPossible(resultCode, resultData, "navigate-top", true /* oomAdj */);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -4390,31 +4351,42 @@
         }
     }
 
-    // TODO: Figure-out a way to consolidate with resize() method below.
-    void requestResize(Rect bounds) {
-        mService.resizeStack(mStackId, bounds,
-                true /* allowResizeInDockedMode */, false /* preserveWindows */,
-                false /* animate */, -1 /* animationDuration */);
-    }
-
     // TODO: Can only be called from special methods in ActivityStackSupervisor.
     // Need to consolidate those calls points into this resize method so anyone can call directly.
-    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
+    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
+            boolean preserveWindows, boolean deferResume) {
         if (!updateBoundsAllowed(bounds)) {
             return;
         }
 
-        // Update override configurations of all tasks in the stack.
-        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
-
-        for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
-            final TaskRecord task = mTaskHistory.get(i);
-            if (task.isResizeable()) {
-                task.updateOverrideConfiguration(taskBounds, tempTaskInsetBounds);
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
+        mService.deferWindowLayout();
+        try {
+            // Update override configurations of all tasks in the stack.
+            final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
+            for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+                final TaskRecord task = mTaskHistory.get(i);
+                if (task.isResizeable()) {
+                    if (tempTaskInsetBounds != null && !tempTaskInsetBounds.isEmpty()) {
+                        task.setDisplayedBounds(taskBounds);
+                        task.setBounds(tempTaskInsetBounds);
+                    } else {
+                        task.setDisplayedBounds(null);
+                        task.setBounds(taskBounds);
+                    }
+                }
             }
-        }
 
-        setBounds(bounds);
+            setBounds(bounds);
+
+            if (!deferResume) {
+                ensureVisibleActivitiesConfigurationLocked(
+                        topRunningActivityLocked(), preserveWindows);
+            }
+        } finally {
+            mService.continueWindowLayout();
+            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+        }
     }
 
     void onPipAnimationEndResize() {
@@ -4543,18 +4515,28 @@
      *         then skip running tasks that match those types.
      */
     void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
-            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) {
+            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed,
+            boolean crossUser, ArraySet<Integer> profileIds) {
         boolean focusedStack = mRootActivityContainer.getTopDisplayFocusedStack() == this;
         boolean topTask = true;
+        int userId = UserHandle.getUserId(callingUid);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.getTopActivity() == null) {
                 // Skip if there are no activities in the task
                 continue;
             }
-            if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) {
-                // Skip if the caller can't fetch this task
-                continue;
+            if (task.effectiveUid != callingUid) {
+                if (task.userId != userId && !crossUser && !profileIds.contains(task.userId)) {
+                    // Skip if the caller does not have cross user permission or cannot access
+                    // the task's profile
+                    continue;
+                }
+                if (!allowed && !task.isActivityTypeHome()) {
+                    // Skip if the caller isn't allowed to fetch this task, except for the home
+                    // task which we always return.
+                    continue;
+                }
             }
             if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
                     && task.getActivityType() == ignoreActivityType) {
@@ -4768,6 +4750,7 @@
             task.cleanUpResourcesForDestroy();
         }
 
+        final ActivityDisplay display = getDisplay();
         if (mTaskHistory.isEmpty()) {
             if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
             // We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -4776,11 +4759,11 @@
                     && mRootActivityContainer.isTopDisplayFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
                 if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
-                    getDisplay().moveHomeStackToFront(myReason);
+                    display.moveHomeStackToFront(myReason);
                 }
             }
             if (isAttached()) {
-                getDisplay().positionChildAtBottom(this);
+                display.positionChildAtBottom(this);
             }
             if (!isActivityTypeHome() || !isAttached()) {
                 remove();
@@ -4793,6 +4776,9 @@
         if (inPinnedWindowingMode()) {
             mService.getTaskChangeNotificationController().notifyActivityUnpinned();
         }
+        if (display.isSingleTaskInstance()) {
+            mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+        }
     }
 
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
@@ -4816,7 +4802,7 @@
         if (!mStackSupervisor.getLaunchParamsController()
                 .layoutTask(task, info.windowLayout, activity, source, options)
                 && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
-            task.updateOverrideConfiguration(getRequestedOverrideBounds());
+            task.setBounds(getRequestedOverrideBounds());
         }
         task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
@@ -4955,12 +4941,6 @@
         }
     }
 
-
-    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-        if (getTaskStack() == null) return null;
-        return getTaskStack().getPictureInPictureBounds(aspectRatio, null /* currentStackBounds */);
-    }
-
     void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a06b9ce..d151f86 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -200,10 +200,6 @@
     // Used to indicate that a task is removed it should also be removed from recents.
     static final boolean REMOVE_FROM_RECENTS = true;
 
-    // Used to indicate that pausing an activity should occur immediately without waiting for
-    // the activity callback indicating that it has completed pausing
-    static final boolean PAUSE_IMMEDIATELY = true;
-
     /** True if the docked stack is currently being resized. */
     private boolean mDockedStackResizing;
 
@@ -1402,7 +1398,7 @@
         boolean reparented = false;
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
             final Rect bounds = options.getLaunchBounds();
-            task.updateOverrideConfiguration(bounds);
+            task.setBounds(bounds);
 
             ActivityStack stack =
                     mRootActivityContainer.getLaunchStack(null, options, task, ON_TOP);
@@ -1416,10 +1412,9 @@
                 // task.reparent() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
             }
-            if (stack.resizeStackWithLaunchBounds()) {
-                mRootActivityContainer.resizeStack(stack, bounds, null /* tempTaskBounds */,
-                        null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
-                        true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            if (stack.shouldResizeStackWithLaunchBounds()) {
+                stack.resize(bounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        !PRESERVE_WINDOWS, !DEFER_RESUME);
             } else {
                 // WM resizeTask must be done after the task is moved to the correct stack,
                 // because Task's setBounds() also updates dim layer's bounds, but that has
@@ -1500,7 +1495,7 @@
     private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
             int toDisplayId, boolean onTop) {
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             final int windowingMode = fromStack.getWindowingMode();
             final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
@@ -1566,7 +1561,7 @@
             mRootActivityContainer.resumeFocusedStacksTopActivities();
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1635,12 +1630,13 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
             mAllowDockedStackResize = false;
             ActivityRecord r = stack.topRunningActivityLocked();
-            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);
+            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,
+                    !PRESERVE_WINDOWS, DEFER_RESUME);
 
             // TODO: Checking for isAttached might not be needed as if the user passes in null
             // dockedBounds then they want the docked stack to be dismissed.
@@ -1678,11 +1674,19 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */);
 
-                    mRootActivityContainer.resizeStack(current,
-                            !tempRect.isEmpty() ? tempRect : null,
+                    if (tempRect.isEmpty()) {
+                        // If this scenario is hit, it means something is not working right.
+                        // Empty/null bounds implies fullscreen. In the event that this stack
+                        // *should* be fullscreen, its mode should be set explicitly in a form
+                        // of setWindowingMode so that other parts of the system are updated
+                        // properly.
+                        throw new IllegalArgumentException("Trying to set null bounds on a"
+                                + " non-fullscreen stack");
+                    }
+
+                    current.resize(tempRect,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
-                            tempOtherTaskInsetBounds, preserveWindows,
-                            true /* allowResizeInDockedMode */, deferResume);
+                            tempOtherTaskInsetBounds, preserveWindows, deferResume);
                 }
             }
             if (!deferResume) {
@@ -1690,7 +1694,7 @@
             }
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1714,9 +1718,8 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
-            ActivityRecord r = stack.topRunningActivityLocked();
             Rect insetBounds = null;
             if (tempPinnedTaskBounds != null && stack.isAnimatingBoundsToFullscreen()) {
                 // Use 0,0 as the position for the inset rect because we are headed for fullscreen.
@@ -1732,10 +1735,10 @@
                 // transitioning it from fullscreen into a floating state.
                 stack.onPipAnimationEndResize();
             }
-            stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds);
-            stack.ensureVisibleActivitiesConfigurationLocked(r, false);
+            stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds, !PRESERVE_WINDOWS,
+                    !DEFER_RESUME);
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1778,30 +1781,19 @@
     }
 
     /**
-     * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
-     */
-    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            String reason) {
-        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
-                reason);
-    }
-
-    /**
      * Removes the task with the specified task id.
      *
      * @param taskId Identifier of the task to be removed.
      * @param killProcess Kill any process associated with the task if possible.
      * @param removeFromRecents Whether to also remove the task from recents.
-     * @param pauseImmediately Pauses all task activities immediately without waiting for the
-     *                         pause-complete callback from the activity.
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            boolean pauseImmediately, String reason) {
+            String reason) {
         final TaskRecord tr =
                 mRootActivityContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
-            tr.removeTaskActivitiesLocked(pauseImmediately, reason);
+            tr.removeTaskActivitiesLocked(reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             mService.getLockTaskController().clearLockedTask(tr);
             if (tr.isPersistable) {
@@ -1925,7 +1917,7 @@
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
             removeTaskByIdLocked(task.taskId, killProcess, false /* removeFromRecents */,
-                    !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+                    "recent-task-trimmed");
         }
         task.removedFromRecents();
     }
@@ -2488,19 +2480,17 @@
     }
 
     void activityRelaunchedLocked(IBinder token) {
-        mWindowManager.notifyAppRelaunchingFinished(token);
         final ActivityRecord r = ActivityRecord.isInStackLocked(token);
         if (r != null) {
+            if (r.mAppWindowToken != null) {
+                r.mAppWindowToken.finishRelaunching();
+            }
             if (r.getActivityStack().shouldSleepOrShutDownActivities()) {
                 r.setSleeping(true, true);
             }
         }
     }
 
-    void activityRelaunchingLocked(ActivityRecord r) {
-        mWindowManager.notifyAppRelaunching(r.appToken);
-    }
-
     void logStackState() {
         mActivityMetricsLogger.logWindowState();
     }
@@ -2738,7 +2728,7 @@
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateStateLocked(
@@ -2829,7 +2819,7 @@
                     mWindowManager.checkSplitScreenMinimizedChanged(false /* animate */);
                 }
             }
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 48bc963..dbf06a5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -81,7 +81,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
-import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
 
 import android.annotation.NonNull;
@@ -1401,7 +1400,7 @@
         int result = START_CANCELED;
         final ActivityStack startedActivityStack;
         try {
-            mService.mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                     startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
         } finally {
@@ -1437,7 +1436,7 @@
                     startedActivityStack.remove();
                 }
             }
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         postStartActivityProcessing(r, result, startedActivityStack);
@@ -1502,205 +1501,60 @@
             mSupervisor.mRecentTasks.setFreezeTaskListReordering();
         }
 
-        // Do not start home activity if it cannot be launched on preferred display. We are not
-        // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
-        // fallback to launch on other displays.
-        if (r.isActivityTypeHome() && !mRootActivityContainer.canStartHomeOnDisplay(r.info,
-                mPreferredDisplayId, true /* allowInstrumenting */)) {
-            Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId);
-            return START_CANCELED;
+        // Compute if there is an existing task that should be used for.
+        final TaskRecord targetTask = computeTargetTask(reusedActivity);
+        final boolean newTask = targetTask == null;
+
+        // Check if starting activity on given task or on a new task is allowed.
+        int startResult = isAllowedToStart(r, newTask, targetTask);
+        if (startResult != START_SUCCESS) {
+            return startResult;
         }
 
-        if (reusedActivity != null) {
-            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
-            // still needs to be a lock task mode violation since the task gets cleared out and
-            // the device would otherwise leave the locked task.
-            if (mService.getLockTaskController().isLockTaskModeViolation(
-                    reusedActivity.getTaskRecord(),
-                    (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
-                Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
-                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+        final ActivityRecord targetTaskTop = newTask ? null : targetTask.getTopActivity();
+        if (targetTaskTop != null) {
+            // Recycle the target task for this launch.
+            startResult = recycleTask(targetTask, targetTaskTop, reusedActivity, outActivity);
+            if (startResult != START_SUCCESS) {
+                return startResult;
             }
-
-            // True if we are clearing top and resetting of a standard (default) launch mode
-            // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
-            final boolean clearTopAndResetStandardLaunchMode =
-                    (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
-                            == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-                    && mLaunchMode == LAUNCH_MULTIPLE;
-
-            // If mStartActivity does not have a task associated with it, associate it with the
-            // reused activity's task. Do not do so if we're clearing top and resetting for a
-            // standard launchMode activity.
-            if (mStartActivity.getTaskRecord() == null && !clearTopAndResetStandardLaunchMode) {
-                mStartActivity.setTask(reusedActivity.getTaskRecord());
-            }
-
-            if (reusedActivity.getTaskRecord().intent == null) {
-                // This task was started because of movement of the activity based on affinity...
-                // Now that we are actually launching it, we can assign the base intent.
-                reusedActivity.getTaskRecord().setIntent(mStartActivity);
-            } else {
-                final boolean taskOnHome =
-                        (mStartActivity.intent.getFlags() & FLAG_ACTIVITY_TASK_ON_HOME) != 0;
-                if (taskOnHome) {
-                    reusedActivity.getTaskRecord().intent.addFlags(FLAG_ACTIVITY_TASK_ON_HOME);
-                } else {
-                    reusedActivity.getTaskRecord().intent.removeFlags(FLAG_ACTIVITY_TASK_ON_HOME);
-                }
-            }
-
-            // This code path leads to delivering a new intent, we want to make sure we schedule it
-            // as the first operation, in case the activity will be resumed as a result of later
-            // operations.
-            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
-                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
-                final TaskRecord task = reusedActivity.getTaskRecord();
-
-                // In this situation we want to remove all activities from the task up to the one
-                // being started. In most cases this means we are resetting the task to its initial
-                // state.
-                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
-                        mLaunchFlags);
-
-                // The above code can remove {@code reusedActivity} from the task, leading to the
-                // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
-                // task reference is needed in the call below to
-                // {@link setTargetStackAndMoveToFrontIfNeeded}.
-                if (reusedActivity.getTaskRecord() == null) {
-                    reusedActivity.setTask(task);
-                }
-
-                if (top != null) {
-                    if (top.isRootOfTask()) {
-                        // Activity aliases may mean we use different intents for the top activity,
-                        // so make sure the task now has the identity of the new intent.
-                        top.getTaskRecord().setIntent(mStartActivity);
-                    }
-                    deliverNewIntent(top);
-                }
-            }
-
-            mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded
-                    (false /* forceSend */, reusedActivity);
-
-            reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
-
-            final ActivityRecord outResult =
-                    outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
-
-            // When there is a reused activity and the current result is a trampoline activity,
-            // set the reused activity as the result.
-            if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
-                outActivity[0] = reusedActivity;
-            }
-
-            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                // We don't need to start a new activity, and the client said not to do anything
-                // if that is the case, so this is it!  And for paranoia, make sure we have
-                // correctly resumed the top activity.
-                resumeTargetStackIfNeeded();
-                return START_RETURN_INTENT_TO_CALLER;
-            }
-
-            if (reusedActivity != null) {
-                setTaskFromIntentActivity(reusedActivity);
-
-                if (!mAddingToTask && mReuseTask == null) {
-                    // We didn't do anything...  but it was needed (a.k.a., client don't use that
-                    // intent!)  And for paranoia, make sure we have correctly resumed the top activity.
-                    resumeTargetStackIfNeeded();
-                    if (outActivity != null && outActivity.length > 0) {
-                        // The reusedActivity could be finishing, for example of starting an
-                        // activity with FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the
-                        // top running activity in the task instead.
-                        outActivity[0] = reusedActivity.finishing
-                                ? reusedActivity.getTaskRecord().getTopActivity() : reusedActivity;
-                    }
-
-                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
-                }
-            }
-        }
-
-        if (mStartActivity.packageName == null) {
-            if (mStartActivity.resultTo != null) {
-                mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
-                        mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
-            }
-            ActivityOptions.abort(mOptions);
-            return START_CLASS_NOT_FOUND;
         }
 
         // If the activity being launched is the same as the one currently at the top, then
         // we need to check if it should only be launched once.
         final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack();
-        final ActivityRecord topFocused = topStack.getTopActivity();
-        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
-        final boolean dontStart = top != null && mStartActivity.resultTo == null
-                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
-                && top.mUserId == mStartActivity.mUserId
-                && top.attachedToProcess()
-                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
-                // This allows home activity to automatically launch on secondary display when
-                // display added, if home was the top activity on default display, instead of
-                // sending new intent to the home activity on default display.
-                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
-        if (dontStart) {
-            // For paranoia, make sure we have correctly resumed the top activity.
-            topStack.mLastPausedActivity = null;
-            if (mDoResume) {
-                mRootActivityContainer.resumeFocusedStacksTopActivities();
-            }
-            ActivityOptions.abort(mOptions);
-            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                // We don't need to start a new activity, and the client said not to do
-                // anything if that is the case, so this is it!
-                return START_RETURN_INTENT_TO_CALLER;
-            }
-
-            deliverNewIntent(top);
-
-            // Don't use mStartActivity.task to show the toast. We're not starting a new activity
-            // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
-            mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(), preferredWindowingMode,
-                    mPreferredDisplayId, topStack);
-
-            return START_DELIVERED_TO_TOP;
+        startResult = deliverToCurrentTopIfNeeded(topStack);
+        if (startResult != START_SUCCESS) {
+            return startResult;
         }
 
-        boolean newTask = false;
-        final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
-                ? mSourceRecord.getTaskRecord() : null;
-
-        // Should this be considered a new task?
-        int result = START_SUCCESS;
-        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
-                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
-            newTask = true;
-            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
-        } else if (mSourceRecord != null) {
-            result = setTaskFromSourceRecord();
-        } else if (mInTask != null) {
-            result = setTaskFromInTask();
-        } else {
-            // This not being started from an existing activity, and not part of a new task...
-            // just put it in the top task, though these days this case should never happen.
-            result = setTaskToCurrentTopOrCreateNewTask();
+        if (mTargetStack == null) {
+            mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
         }
-        if (result != START_SUCCESS) {
-            return result;
+        if (newTask) {
+            final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
+                    ? mSourceRecord.getTaskRecord() : null;
+            setNewTask(taskToAffiliate);
+            if (mService.getLockTaskController().isLockTaskModeViolation(
+                    mStartActivity.getTaskRecord())) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+        } else if (mAddingToTask) {
+            addOrReparentStartingActivity(targetTask, "adding to task");
+        }
+
+        if (!mAvoidMoveToFront && mDoResume) {
+            mTargetStack.moveToFront("reuseOrNewTask");
         }
 
         mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
-        mService.getPackageManagerInternalLocked().grantEphemeralAccess(
+        mService.getPackageManagerInternalLocked().grantImplicitAccess(
                 mStartActivity.mUserId, mIntent,
-                UserHandle.getAppId(mStartActivity.info.applicationInfo.uid),
-                UserHandle.getAppId(mCallingUid));
+                mCallingUid,
+                UserHandle.getAppId(mStartActivity.info.applicationInfo.uid)
+        );
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
                     mStartActivity.getTaskRecord().taskId);
@@ -1712,8 +1566,8 @@
         mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
                 false /* forceSend */, mStartActivity);
 
-        mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
-                mOptions);
+        mTargetStack.startActivityLocked(mStartActivity, topStack.getTopActivity(), newTask,
+                mKeepCurTransition, mOptions);
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTaskRecord().topRunningActivityLocked();
@@ -1754,6 +1608,312 @@
         return START_SUCCESS;
     }
 
+    private TaskRecord computeTargetTask(ActivityRecord reusedActivity) {
+        if (reusedActivity != null) {
+            return reusedActivity.getTaskRecord();
+        } else if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
+                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+            // A new task should be created instead of using existing one.
+            return null;
+        } else if (mSourceRecord != null) {
+            return mSourceRecord.getTaskRecord();
+        } else if (mInTask != null) {
+            return mInTask;
+        } else {
+            final ActivityRecord top = computeStackFocus(mStartActivity, false /* newTask */,
+                    mLaunchFlags, mOptions).getTopActivity();
+            if (top != null) {
+                return top.getTaskRecord();
+            }
+        }
+        return null;
+    }
+
+    private int isAllowedToStart(ActivityRecord r, boolean newTask, TaskRecord targetTask) {
+        if (mStartActivity.packageName == null) {
+            if (mStartActivity.resultTo != null) {
+                mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+                        mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
+            }
+            ActivityOptions.abort(mOptions);
+            return START_CLASS_NOT_FOUND;
+        }
+
+        // Do not start home activity if it cannot be launched on preferred display. We are not
+        // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
+        // fallback to launch on other displays.
+        if (r.isActivityTypeHome() && !mRootActivityContainer.canStartHomeOnDisplay(r.info,
+                mPreferredDisplayId, true /* allowInstrumenting */)) {
+            Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId);
+            return START_CANCELED;
+        }
+
+        if (mRestrictedBgActivity && (newTask || !targetTask.containsAppUid(mCallingUid))
+                && handleBackgroundActivityAbort(mStartActivity)) {
+            Slog.e(TAG, "Abort background activity starts from " + mCallingUid);
+            return START_ABORTED;
+        }
+
+        // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but still
+        // needs to be a lock task mode violation since the task gets cleared out and the device
+        // would otherwise leave the locked task.
+        final boolean isNewClearTask =
+                (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                        == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        if (!newTask && mService.getLockTaskController().isLockTaskModeViolation(targetTask,
+                isNewClearTask)) {
+            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+        }
+
+        return START_SUCCESS;
+    }
+
+    /**
+     * Prepare the target task to be reused for this launch, which including:
+     * - Position the target task on valid stack on preferred display.
+     * - Comply to the specified activity launch flags
+     * - Determine whether need to add a new activity on top or just brought the task to front.
+     */
+    private int recycleTask(TaskRecord targetTask, ActivityRecord targetTaskTop,
+            ActivityRecord reusedActivity, ActivityRecord[] outActivity) {
+        // True if we are clearing top and resetting of a standard (default) launch mode
+        // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
+        final boolean clearTopAndResetStandardLaunchMode =
+                (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
+                        == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                        && mLaunchMode == LAUNCH_MULTIPLE;
+
+        // If mStartActivity does not have a task associated with it, associate it with the
+        // reused activity's task. Do not do so if we're clearing top and resetting for a
+        // standard launchMode activity.
+        if (mStartActivity.getTaskRecord() == null && !clearTopAndResetStandardLaunchMode) {
+            mStartActivity.setTask(targetTask);
+        }
+
+        if (reusedActivity != null) {
+            if (targetTask.intent == null) {
+                // This task was started because of movement of the activity based on
+                // affinity...
+                // Now that we are actually launching it, we can assign the base intent.
+                targetTask.setIntent(mStartActivity);
+            } else {
+                final boolean taskOnHome =
+                        (mStartActivity.intent.getFlags() & FLAG_ACTIVITY_TASK_ON_HOME) != 0;
+                if (taskOnHome) {
+                    targetTask.intent.addFlags(FLAG_ACTIVITY_TASK_ON_HOME);
+                } else {
+                    targetTask.intent.removeFlags(FLAG_ACTIVITY_TASK_ON_HOME);
+                }
+            }
+        }
+
+        mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(false /* forceSend */,
+                targetTaskTop);
+
+        setTargetStackIfNeeded(targetTaskTop);
+
+        final ActivityRecord outResult =
+                outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
+
+        // When there is a reused activity and the current result is a trampoline activity,
+        // set the reused activity as the result.
+        if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
+            outActivity[0] = targetTaskTop;
+        }
+
+        if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
+            // We don't need to start a new activity, and the client said not to do anything
+            // if that is the case, so this is it!  And for paranoia, make sure we have
+            // correctly resumed the top activity.
+            if (!mMovedToFront && mDoResume) {
+                if (DEBUG_TASKS) {
+                    Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
+                            + " from " + targetTaskTop);
+                }
+                mTargetStack.moveToFront("intentActivityFound");
+            }
+            resumeTargetStackIfNeeded();
+            return START_RETURN_INTENT_TO_CALLER;
+        }
+
+        complyActivityFlags(targetTask, reusedActivity);
+
+        if (mAddingToTask) {
+            return START_SUCCESS;
+        }
+
+        if (!mMovedToFront && mDoResume) {
+            mTargetStack.moveToFront("intentActivityFound");
+        }
+        // We didn't do anything...  but it was needed (a.k.a., client don't use that intent!)
+        // And for paranoia, make sure we have correctly resumed the top activity.
+        resumeTargetStackIfNeeded();
+        if (outActivity != null && outActivity.length > 0) {
+            // The reusedActivity could be finishing, for example of starting an activity with
+            // FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the top running activity in the
+            // task instead.
+            outActivity[0] = targetTaskTop.finishing ? targetTask.getTopActivity() : targetTaskTop;
+        }
+        return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
+    }
+
+    /**
+     * Check if the activity being launched is the same as the one currently at the top and it
+     * should only be launched once.
+     */
+    private int deliverToCurrentTopIfNeeded(ActivityStack topStack) {
+        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
+        final boolean dontStart = top != null && mStartActivity.resultTo == null
+                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
+                && top.mUserId == mStartActivity.mUserId
+                && top.attachedToProcess()
+                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
+                // This allows home activity to automatically launch on secondary display when
+                // display added, if home was the top activity on default display, instead of
+                // sending new intent to the home activity on default display.
+                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
+        if (!dontStart) {
+            return START_SUCCESS;
+        }
+
+        // For paranoia, make sure we have correctly resumed the top activity.
+        topStack.mLastPausedActivity = null;
+        if (mDoResume) {
+            mRootActivityContainer.resumeFocusedStacksTopActivities();
+        }
+        ActivityOptions.abort(mOptions);
+        if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
+            // We don't need to start a new activity, and the client said not to do anything if
+            // that is the case, so this is it!
+            return START_RETURN_INTENT_TO_CALLER;
+        }
+
+        deliverNewIntent(top);
+
+        // Don't use mStartActivity.task to show the toast. We're not starting a new activity but
+        // reusing 'top'. Fields in mStartActivity may not be fully initialized.
+        mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(),
+                mLaunchParams.mWindowingMode, mPreferredDisplayId, topStack);
+
+        return START_DELIVERED_TO_TOP;
+    }
+
+    /**
+     * Applying the launching flags to the task, which might clear few or all the activities in the
+     * task.
+     */
+    private void complyActivityFlags(TaskRecord targetTask, ActivityRecord reusedActivity) {
+        ActivityRecord targetTaskTop = targetTask.getTopActivity();
+        if (reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+            targetTaskTop = mTargetStack.resetTaskIfNeededLocked(targetTaskTop, mStartActivity);
+        }
+
+        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
+            // The caller has requested to completely replace any existing task with its new
+            // activity. Well that should not be too hard...
+            // Note: we must persist the {@link TaskRecord} first as intentActivity could be
+            // removed from calling performClearTaskLocked (For example, if it is being brought out
+            // of history or if it is finished immediately), thus disassociating the task. Also note
+            // that mReuseTask is reset as a result of {@link TaskRecord#performClearTaskLocked}
+            // launching another activity.
+            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
+            // already launching one.
+            targetTask.performClearTaskLocked();
+            targetTask.setIntent(mStartActivity);
+            mAddingToTask = true;
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
+                || isDocumentLaunchesIntoExisting(mLaunchFlags)
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
+            // In this situation we want to remove all activities from the task up to the one
+            // being started. In most cases this means we are resetting the task to its initial
+            // state.
+            final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
+                    mLaunchFlags);
+
+            // The above code can remove {@code reusedActivity} from the task, leading to the
+            // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
+            // task reference is needed in the call below to
+            // {@link setTargetStackAndMoveToFrontIfNeeded}.
+            if (targetTaskTop.getTaskRecord() == null) {
+                targetTaskTop.setTask(targetTask);
+            }
+
+            if (top != null) {
+                if (top.isRootOfTask()) {
+                    // Activity aliases may mean we use different intents for the top activity,
+                    // so make sure the task now has the identity of the new intent.
+                    top.getTaskRecord().setIntent(mStartActivity);
+                }
+                deliverNewIntent(top);
+            } else {
+                // A special case: we need to start the activity because it is not currently
+                // running, and the caller has asked to clear the current task to have this
+                // activity at the top.
+                mAddingToTask = true;
+                if (targetTask.getStack() == null) {
+                    // Target stack got cleared when we all activities were removed above.
+                    // Go ahead and reset it.
+                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
+                            mLaunchFlags, mOptions);
+                    mTargetStack.addTask(targetTask,
+                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
+                }
+            }
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) == 0 && !mAddingToTask
+                && (mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
+            // In this case, we are launching an activity in our own task that may
+            // already be running somewhere in the history, and we want to shuffle it to
+            // the front of the stack if so.
+            final ActivityRecord act = targetTask.findActivityInHistoryLocked(
+                    mStartActivity);
+            if (act != null) {
+                final TaskRecord task = act.getTaskRecord();
+                task.moveActivityToFrontLocked(act);
+                act.updateOptionsLocked(mOptions);
+                deliverNewIntent(act);
+                mTargetStack.mLastPausedActivity = null;
+            } else {
+                mAddingToTask = true;
+            }
+        } else if (mStartActivity.mActivityComponent.equals(targetTask.realActivity)) {
+            // In this case the top activity on the task is the same as the one being launched,
+            // so we take that as a request to bring the task to the foreground. If the top
+            // activity in the task is the root activity, deliver this new intent to it if it
+            // desires.
+            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                    || LAUNCH_SINGLE_TOP == mLaunchMode)
+                    && targetTaskTop.mActivityComponent.equals(
+                    mStartActivity.mActivityComponent) && mStartActivity.resultTo == null) {
+                if (targetTaskTop.isRootOfTask()) {
+                    targetTaskTop.getTaskRecord().setIntent(mStartActivity);
+                }
+                deliverNewIntent(targetTaskTop);
+            } else if (!targetTask.isSameIntentFilter(mStartActivity)) {
+                // In this case we are launching the root activity of the task, but with a
+                // different intent. We should start a new instance on top.
+                mAddingToTask = true;
+            } else if (reusedActivity == null) {
+                mAddingToTask = true;
+            }
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+            // In this case an activity is being launched in to an existing task, without
+            // resetting that task. This is typically the situation of launching an activity
+            // from a notification or shortcut. We want to place the new activity on top of the
+            // current task.
+            mAddingToTask = true;
+        } else if (!targetTask.rootWasReset) {
+            // In this case we are launching into an existing task that has not yet been started
+            // from its front door. The current task has been brought to the front. Ideally,
+            // we'd probably like to place this new task at the bottom of its stack, but that's
+            // a little hard to do with the current organization of the code so for now we'll
+            // just drop it.
+            targetTask.setIntent(mStartActivity);
+        }
+    }
+
     /**
      * Resets the {@link ActivityStarter} state.
      * @param clearRequest whether the request should be reset to default values.
@@ -2106,7 +2266,7 @@
      * @param intentActivity Existing matching activity.
      * @return {@link ActivityRecord} brought to front.
      */
-    private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
+    private void setTargetStackIfNeeded(ActivityRecord intentActivity) {
         mTargetStack = intentActivity.getActivityStack();
         mTargetStack.mLastPausedActivity = null;
         // If the target task is not in the front, then we need to bring it to the front...
@@ -2136,167 +2296,71 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTaskRecord());
                 }
 
-                // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
-                // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
-                // So no point resuming any of the activities here, it just wastes one extra
-                // resuming, plus enter AND exit transitions.
-                // Here we only want to bring the target stack forward. Transition will be applied
-                // to the new activity that's started after the old ones are gone.
-                final boolean willClearTask =
-                        (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-                if (!willClearTask) {
-                    final ActivityStack launchStack = getLaunchStack(
-                            mStartActivity, mLaunchFlags, mStartActivity.getTaskRecord(), mOptions);
-                    final TaskRecord intentTask = intentActivity.getTaskRecord();
-                    if (launchStack == null || launchStack == mTargetStack) {
-                        // We only want to move to the front, if we aren't going to launch on a
-                        // different stack. If we launch on a different stack, we will put the
-                        // task on top there.
-                        mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
-                                mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
-                        mMovedToFront = true;
-                    } else if (launchStack.inSplitScreenWindowingMode()) {
-                        if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
-                            // If we want to launch adjacent and mTargetStack is not the computed
-                            // launch stack - move task to top of computed stack.
-                            intentTask.reparent(launchStack, ON_TOP,
-                                    REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                    "launchToSide");
-                        } else {
-                            // TODO: This should be reevaluated in MW v2.
-                            // We choose to move task to front instead of launching it adjacent
-                            // when specific stack was requested explicitly and it appeared to be
-                            // adjacent stack, but FLAG_ACTIVITY_LAUNCH_ADJACENT was not set.
-                            mTargetStack.moveTaskToFrontLocked(intentTask,
-                                    mNoAnimation, mOptions, mStartActivity.appTimeTracker,
-                                    "bringToFrontInsteadOfAdjacentLaunch");
-                        }
-                        mMovedToFront = launchStack != launchStack.getDisplay()
-                                .getTopStackInWindowingMode(launchStack.getWindowingMode());
-                    } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
-                        // Target and computed stacks are on different displays and we've
-                        // found a matching task - move the existing instance to that display and
-                        // move it to front.
-                        intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                final ActivityStack launchStack = getLaunchStack(
+                        mStartActivity, mLaunchFlags, mStartActivity.getTaskRecord(), mOptions);
+                final TaskRecord intentTask = intentActivity.getTaskRecord();
+                if (launchStack == null || launchStack == mTargetStack) {
+                    // We only want to move to the front, if we aren't going to launch on a
+                    // different stack. If we launch on a different stack, we will put the
+                    // task on top there.
+                    mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
+                            mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
+                    mMovedToFront = true;
+                } else if (launchStack.inSplitScreenWindowingMode()) {
+                    if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+                        // If we want to launch adjacent and mTargetStack is not the computed
+                        // launch stack - move task to top of computed stack.
+                        intentTask.reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                "reparentToDisplay");
-                        mMovedToFront = true;
-                    } else if (launchStack.isActivityTypeHome()
-                            && !mTargetStack.isActivityTypeHome()) {
-                        // It is possible for the home activity to be in another stack initially.
-                        // For example, the activity may have been initially started with an intent
-                        // which placed it in the fullscreen stack. To ensure the proper handling of
-                        // the activity based on home stack assumptions, we must move it over.
-                        intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
-                                REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                "reparentingHome");
-                        mMovedToFront = true;
+                                "launchToSide");
+                    } else {
+                        // TODO: This should be reevaluated in MW v2.
+                        // We choose to move task to front instead of launching it adjacent
+                        // when specific stack was requested explicitly and it appeared to be
+                        // adjacent stack, but FLAG_ACTIVITY_LAUNCH_ADJACENT was not set.
+                        mTargetStack.moveTaskToFrontLocked(intentTask,
+                                mNoAnimation, mOptions, mStartActivity.appTimeTracker,
+                                "bringToFrontInsteadOfAdjacentLaunch");
                     }
-                    mOptions = null;
-
-                    // We are moving a task to the front, use starting window to hide initial drawn
-                    // delay.
-                    intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
-                            true /* taskSwitch */);
+                    mMovedToFront = launchStack != launchStack.getDisplay()
+                            .getTopStackInWindowingMode(launchStack.getWindowingMode());
+                } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
+                    // Target and computed stacks are on different displays and we've
+                    // found a matching task - move the existing instance to that display and
+                    // move it to front.
+                    intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                            REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                            "reparentToDisplay");
+                    mMovedToFront = true;
+                } else if (launchStack.isActivityTypeHome()
+                        && !mTargetStack.isActivityTypeHome()) {
+                    // It is possible for the home activity to be in another stack initially.
+                    // For example, the activity may have been initially started with an intent
+                    // which placed it in the fullscreen stack. To ensure the proper handling of
+                    // the activity based on home stack assumptions, we must move it over.
+                    intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                            REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                            "reparentingHome");
+                    mMovedToFront = true;
+                } else if (launchStack.topTask() == null) {
+                    // The task does not need to be reparented to the launch stack. Remove the
+                    // launch stack if there is no activity in it.
+                    launchStack.remove();
                 }
+
+                mOptions = null;
+
+                // We are moving a task to the front, use starting window to hide initial drawn
+                // delay.
+                intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
+                        true /* taskSwitch */);
             }
         }
         // Need to update mTargetStack because if task was moved out of it, the original stack may
         // be destroyed.
         mTargetStack = intentActivity.getActivityStack();
-        if (!mMovedToFront && mDoResume) {
-            if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
-                    + " from " + intentActivity);
-            mTargetStack.moveToFront("intentActivityFound");
-        }
-
         mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTaskRecord(),
                 WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
-
-        // If the caller has requested that the target task be reset, then do so.
-        if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
-            return mTargetStack.resetTaskIfNeededLocked(intentActivity, mStartActivity);
-        }
-        return intentActivity;
-    }
-
-    private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
-        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
-            // The caller has requested to completely replace any existing task with its new
-            // activity. Well that should not be too hard...
-            // Note: we must persist the {@link TaskRecord} first as intentActivity could be
-            // removed from calling performClearTaskLocked (For example, if it is being brought out
-            // of history or if it is finished immediately), thus disassociating the task. Also note
-            // that mReuseTask is reset as a result of {@link TaskRecord#performClearTaskLocked}
-            // launching another activity.
-            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
-            // already launching one.
-            final TaskRecord task = intentActivity.getTaskRecord();
-            task.performClearTaskLocked();
-            mReuseTask = task;
-            mReuseTask.setIntent(mStartActivity);
-        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
-            final ActivityRecord top = intentActivity.getTaskRecord().performClearTaskLocked(
-                    mStartActivity, mLaunchFlags);
-            if (top == null) {
-                // A special case: we need to start the activity because it is not currently
-                // running, and the caller has asked to clear the current task to have this
-                // activity at the top.
-                mAddingToTask = true;
-
-                // We are no longer placing the activity in the task we previously thought we were.
-                mStartActivity.setTask(null);
-                // Now pretend like this activity is being started by the top of its task, so it
-                // is put in the right place.
-                mSourceRecord = intentActivity;
-                final TaskRecord task = mSourceRecord.getTaskRecord();
-                if (task != null && task.getStack() == null) {
-                    // Target stack got cleared when we all activities were removed above.
-                    // Go ahead and reset it.
-                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
-                            mLaunchFlags, mOptions);
-                    mTargetStack.addTask(task,
-                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
-                }
-            }
-        } else if (mStartActivity.mActivityComponent.equals(
-                intentActivity.getTaskRecord().realActivity)) {
-            // In this case the top activity on the task is the same as the one being launched,
-            // so we take that as a request to bring the task to the foreground. If the top
-            // activity in the task is the root activity, deliver this new intent to it if it
-            // desires.
-            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                        || LAUNCH_SINGLE_TOP == mLaunchMode)
-                    && intentActivity.mActivityComponent.equals(
-                            mStartActivity.mActivityComponent)) {
-                if (intentActivity.isRootOfTask()) {
-                    intentActivity.getTaskRecord().setIntent(mStartActivity);
-                }
-                deliverNewIntent(intentActivity);
-            } else if (!intentActivity.getTaskRecord().isSameIntentFilter(mStartActivity)) {
-                // In this case we are launching the root activity of the task, but with a
-                // different intent. We should start a new instance on top.
-                mAddingToTask = true;
-                mSourceRecord = intentActivity;
-            }
-        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
-            // In this case an activity is being launched in to an existing task, without
-            // resetting that task. This is typically the situation of launching an activity
-            // from a notification or shortcut. We want to place the new activity on top of the
-            // current task.
-            mAddingToTask = true;
-            mSourceRecord = intentActivity;
-        } else if (!intentActivity.getTaskRecord().rootWasReset) {
-            // In this case we are launching into an existing task that has not yet been started
-            // from its front door. The current task has been brought to the front. Ideally,
-            // we'd probably like to place this new task at the bottom of its stack, but that's
-            // a little hard to do with the current organization of the code so for now we'll
-            // just drop it.
-            intentActivity.getTaskRecord().setIntent(mStartActivity);
-        }
     }
 
     private void resumeTargetStackIfNeeded() {
@@ -2308,47 +2372,24 @@
         mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);
     }
 
-    private int setTaskFromReuseOrCreateNewTask(TaskRecord taskToAffiliate) {
-        if (mRestrictedBgActivity && (mReuseTask == null || !mReuseTask.containsAppUid(mCallingUid))
-                && handleBackgroundActivityAbort(mStartActivity)) {
-            return START_ABORTED;
-        }
+    private void setNewTask(TaskRecord taskToAffiliate) {
+        final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
+        final TaskRecord task = mTargetStack.createTaskRecord(
+                mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId),
+                mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
+                mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
+                mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
+        addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
+        updateBounds(mStartActivity.getTaskRecord(), mLaunchParams.mBounds);
 
-        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
-
-        // Do no move the target stack to front yet, as we might bail if
-        // isLockTaskModeViolation fails below.
-
-        if (mReuseTask == null) {
-            final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
-            final TaskRecord task = mTargetStack.createTaskRecord(
-                    mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId),
-                    mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
-                    mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
-                    mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
-            addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
-            updateBounds(mStartActivity.getTaskRecord(), mLaunchParams.mBounds);
-
-            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+        if (DEBUG_TASKS) {
+            Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                     + " in new task " + mStartActivity.getTaskRecord());
-        } else {
-            addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");
         }
 
         if (taskToAffiliate != null) {
             mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
         }
-
-        if (mService.getLockTaskController().isLockTaskModeViolation(
-                mStartActivity.getTaskRecord())) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        if (mDoResume) {
-            mTargetStack.moveToFront("reuseOrNewTask");
-        }
-        return START_SUCCESS;
     }
 
     private void deliverNewIntent(ActivityRecord activity) {
@@ -2362,166 +2403,6 @@
         mIntentDelivered = true;
     }
 
-    private int setTaskFromSourceRecord() {
-        if (mService.getLockTaskController().isLockTaskModeViolation(
-                mSourceRecord.getTaskRecord())) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        final TaskRecord sourceTask = mSourceRecord.getTaskRecord();
-        final ActivityStack sourceStack = mSourceRecord.getActivityStack();
-        if (mRestrictedBgActivity && !sourceTask.containsAppUid(mCallingUid)) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        // We only want to allow changing stack in two cases:
-        // 1. If the target task is not the top one. Otherwise we would move the launching task to
-        //    the other side, rather than show two side by side.
-        // 2. If activity is not allowed on target display.
-        final int targetDisplayId = mTargetStack != null ? mTargetStack.mDisplayId
-                : sourceStack.mDisplayId;
-        final boolean moveStackAllowed = sourceStack.topTask() != sourceTask
-                || !mStartActivity.canBeLaunchedOnDisplay(targetDisplayId);
-        if (moveStackAllowed) {
-            mTargetStack = getLaunchStack(mStartActivity, mLaunchFlags,
-                    mStartActivity.getTaskRecord(), mOptions);
-            // If target stack is not found now - we can't just rely on the source stack, as it may
-            // be not suitable. Let's check other displays.
-            if (mTargetStack == null && targetDisplayId != sourceStack.mDisplayId) {
-                // Can't use target display, lets find a stack on the source display.
-                mTargetStack = mRootActivityContainer.getValidLaunchStackOnDisplay(
-                        sourceStack.mDisplayId, mStartActivity, mOptions, mLaunchParams);
-            }
-            if (mTargetStack == null) {
-                // There are no suitable stacks on the target and source display(s). Look on all
-                // displays.
-                mTargetStack = mRootActivityContainer.getNextValidLaunchStack(
-                        mStartActivity, -1 /* currentFocus */);
-            }
-        }
-
-        if (mTargetStack == null) {
-            mTargetStack = sourceStack;
-        } else if (mTargetStack != sourceStack) {
-            sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                    DEFER_RESUME, "launchToSide");
-        }
-
-        final TaskRecord topTask = mTargetStack.topTask();
-        if (topTask != sourceTask && !mAvoidMoveToFront) {
-            mTargetStack.moveTaskToFrontLocked(sourceTask, mNoAnimation, mOptions,
-                    mStartActivity.appTimeTracker, "sourceTaskToFront");
-        } else if (mDoResume) {
-            mTargetStack.moveToFront("sourceStackToFront");
-        }
-
-        if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0) {
-            // In this case, we are adding the activity to an existing task, but the caller has
-            // asked to clear that task if the activity is already running.
-            ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags);
-            mKeepCurTransition = true;
-            if (top != null) {
-                mStartActivity.logStartActivity(AM_NEW_INTENT, top.getTaskRecord());
-                deliverNewIntent(top);
-                // For paranoia, make sure we have correctly resumed the top activity.
-                mTargetStack.mLastPausedActivity = null;
-                if (mDoResume) {
-                    mRootActivityContainer.resumeFocusedStacksTopActivities();
-                }
-                ActivityOptions.abort(mOptions);
-                return START_DELIVERED_TO_TOP;
-            }
-        } else if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
-            // In this case, we are launching an activity in our own task that may already be
-            // running somewhere in the history, and we want to shuffle it to the front of the
-            // stack if so.
-            final ActivityRecord top = sourceTask.findActivityInHistoryLocked(mStartActivity);
-            if (top != null) {
-                final TaskRecord task = top.getTaskRecord();
-                task.moveActivityToFrontLocked(top);
-                top.updateOptionsLocked(mOptions);
-                mStartActivity.logStartActivity(AM_NEW_INTENT, task);
-                deliverNewIntent(top);
-                mTargetStack.mLastPausedActivity = null;
-                if (mDoResume) {
-                    mRootActivityContainer.resumeFocusedStacksTopActivities();
-                }
-                return START_DELIVERED_TO_TOP;
-            }
-        }
-
-        // An existing activity is starting this new activity, so we want to keep the new one in
-        // the same task as the one that is starting it.
-        addOrReparentStartingActivity(sourceTask, "setTaskFromSourceRecord");
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in existing task " + mStartActivity.getTaskRecord()
-                + " from source " + mSourceRecord);
-        return START_SUCCESS;
-    }
-
-    private int setTaskFromInTask() {
-        // The caller is asking that the new activity be started in an explicit
-        // task it has provided to us.
-        if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        mTargetStack = mInTask.getStack();
-
-        // Check whether we should actually launch the new activity in to the task,
-        // or just reuse the current activity on top.
-        ActivityRecord top = mInTask.getTopActivity();
-        if (top != null && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
-                && top.mUserId == mStartActivity.mUserId) {
-            if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                    || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) {
-                mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
-                        mStartActivity.appTimeTracker, "inTaskToFront");
-                if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                    // We don't need to start a new activity, and the client said not to do
-                    // anything if that is the case, so this is it!
-                    return START_RETURN_INTENT_TO_CALLER;
-                }
-                deliverNewIntent(top);
-                return START_DELIVERED_TO_TOP;
-            }
-        }
-
-        if (!mAddingToTask) {
-            mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
-                    mStartActivity.appTimeTracker, "inTaskToFront");
-            // We don't actually want to have this activity added to the task, so just
-            // stop here but still tell the caller that we consumed the intent.
-            ActivityOptions.abort(mOptions);
-            return START_TASK_TO_FRONT;
-        }
-
-        if (!mLaunchParams.mBounds.isEmpty()) {
-            // TODO: Shouldn't we already know what stack to use by the time we get here?
-            ActivityStack stack = mRootActivityContainer.getLaunchStack(
-                    null, null, mInTask, ON_TOP);
-            if (stack != mInTask.getStack()) {
-                mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                        DEFER_RESUME, "inTaskToFront");
-                mTargetStack = mInTask.getStack();
-            }
-
-            updateBounds(mInTask, mLaunchParams.mBounds);
-        }
-
-        mTargetStack.moveTaskToFrontLocked(
-                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
-
-        addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in explicit task " + mStartActivity.getTaskRecord());
-
-        return START_SUCCESS;
-    }
-
     @VisibleForTesting
     void updateBounds(TaskRecord task, Rect bounds) {
         if (bounds.isEmpty()) {
@@ -2529,41 +2410,13 @@
         }
 
         final ActivityStack stack = task.getStack();
-        if (stack != null && stack.resizeStackWithLaunchBounds()) {
-            mService.resizeStack(
-                    stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        if (stack != null && stack.inPinnedWindowingMode()) {
+            mService.animateResizePinnedStack(stack.mStackId, bounds, -1);
         } else {
-            task.updateOverrideConfiguration(bounds);
+            task.setBounds(bounds);
         }
     }
 
-    private int setTaskToCurrentTopOrCreateNewTask() {
-        mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
-        if (mDoResume) {
-            mTargetStack.moveToFront("addingToTopTask");
-        }
-        final ActivityRecord prev = mTargetStack.getTopActivity();
-        if (mRestrictedBgActivity && prev == null) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        final TaskRecord task = (prev != null)
-                ? prev.getTaskRecord() : mTargetStack.createTaskRecord(
-                mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId), mStartActivity.info,
-                mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
-        if (mRestrictedBgActivity && prev != null && !task.containsAppUid(mCallingUid)) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
-        mTargetStack.positionChildWindowContainerAtTop(task);
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in new guessed " + mStartActivity.getTaskRecord());
-        return START_SUCCESS;
-    }
-
     private void addOrReparentStartingActivity(TaskRecord parent, String reason) {
         if (mStartActivity.getTaskRecord() == null || mStartActivity.getTaskRecord() == parent) {
             parent.addActivityToTop(mStartActivity);
@@ -2695,7 +2548,8 @@
             final boolean onTop =
                     (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
             final ActivityStack stack =
-                    mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
+                    mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams,
+                            mRequest.realCallingPid, mRequest.realCallingUid);
             return stack;
         }
         // Otherwise handle adjacent launch.
@@ -2813,11 +2667,24 @@
         return this;
     }
 
+    /**
+     * Sets the pid of the caller who originally started the activity.
+     *
+     * Normally, the pid/uid would be the calling pid from the binder call.
+     * However, in case of a {@link PendingIntent}, the pid/uid pair of the caller is considered
+     * the original entity that created the pending intent, in contrast to setRealCallingPid/Uid,
+     * which represents the entity who invoked pending intent via {@link PendingIntent#send}.
+     */
     ActivityStarter setCallingPid(int pid) {
         mRequest.callingPid = pid;
         return this;
     }
 
+    /**
+     * Sets the uid of the caller who originally started the activity.
+     *
+     * @see #setCallingPid
+     */
     ActivityStarter setCallingUid(int uid) {
         mRequest.callingUid = uid;
         return this;
@@ -2828,11 +2695,25 @@
         return this;
     }
 
+    /**
+     * Sets the pid of the caller who requested to launch the activity.
+     *
+     * The pid/uid represents the caller who launches the activity in this request.
+     * It will almost same as setCallingPid/Uid except when processing {@link PendingIntent}:
+     * the pid/uid will be the caller who called {@link PendingIntent#send()}.
+     *
+     * @see #setCallingPid
+     */
     ActivityStarter setRealCallingPid(int pid) {
         mRequest.realCallingPid = pid;
         return this;
     }
 
+    /**
+     * Sets the uid of the caller who requested to launch the activity.
+     *
+     * @see #setRealCallingPid
+     */
     ActivityStarter setRealCallingUid(int uid) {
         mRequest.realCallingUid = uid;
         return this;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3c5947a..468a13d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
@@ -45,7 +47,6 @@
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
 import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
@@ -82,10 +83,11 @@
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.SCREEN_COMPAT_PACKAGES;
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.MODE;
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
@@ -121,6 +123,7 @@
 import static com.android.server.wm.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -212,6 +215,7 @@
 import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -545,7 +549,6 @@
     /** The dimensions of the thumbnails in the Recents UI. */
     private int mThumbnailWidth;
     private int mThumbnailHeight;
-    private float mFullscreenThumbnailScale;
 
     /**
      * Flag that indicates if multi-window is enabled.
@@ -594,6 +597,19 @@
      */
     int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            LAYOUT_REASON_CONFIG_CHANGED,
+            LAYOUT_REASON_VISIBILITY_CHANGED,
+    })
+    @interface LayoutReason {}
+    static final int LAYOUT_REASON_CONFIG_CHANGED = 0x1;
+    static final int LAYOUT_REASON_VISIBILITY_CHANGED = 0x2;
+
+    /** The reasons to perform surface placement. */
+    @LayoutReason
+    private int mLayoutReasons;
+
     // Whether we should show our dialogs (ANR, crash, etc) or just perform their default action
     // automatically. Important for devices without direct input devices.
     private boolean mShowDialogs = true;
@@ -772,15 +788,6 @@
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_height);
-
-            if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
-                mFullscreenThumbnailScale = (float) res
-                        .getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
-                        (float) globalConfig.screenWidthDp;
-            } else {
-                mFullscreenThumbnailScale = res.getFraction(
-                        com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
-            }
         }
     }
 
@@ -1614,8 +1621,7 @@
                     // Explicitly dismissing the activity so reset its relaunch flag.
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
                 } else {
-                    r.finishIfPossible(resultCode, resultData, "app-request",
-                            true /* oomAdj */, !PAUSE_IMMEDIATELY);
+                    r.finishIfPossible(resultCode, resultData, "app-request", true /* oomAdj */);
                     res = r.finishing;
                     if (!res) {
                         Slog.i(TAG, "Failed to finish by app-request");
@@ -1680,7 +1686,6 @@
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
             ActivityRecord.activityResumedLocked(token);
-            mWindowManager.notifyAppResumedFinished(token);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -2508,15 +2513,22 @@
             @WindowConfiguration.ActivityType int ignoreActivityType,
             @WindowConfiguration.WindowingMode int ignoreWindowingMode) {
         final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final boolean crossUser = isCrossUserAllowed(callingPid, callingUid);
+        final int[] profileIds = getUserManager().getProfileIds(
+                UserHandle.getUserId(callingUid), true);
+        ArraySet<Integer> callingProfileIds = new ArraySet<>();
+        for (int i = 0; i < profileIds.length; i++) {
+            callingProfileIds.add(profileIds[i]);
+        }
         ArrayList<ActivityManager.RunningTaskInfo> list = new ArrayList<>();
 
         synchronized (mGlobalLock) {
             if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
 
-            final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
-                    callingUid);
+            final boolean allowed = isGetTasksAllowed("getTasks", callingPid, callingUid);
             mRootActivityContainer.getRunningTasks(maxNum, list, ignoreActivityType,
-                    ignoreWindowingMode, callingUid, allowed);
+                    ignoreWindowingMode, callingUid, allowed, crossUser, callingProfileIds);
         }
 
         return list;
@@ -2582,35 +2594,23 @@
     }
 
     @Override
-    public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+    public void animateResizePinnedStack(int stackId, Rect destBounds, int animationDuration) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "animateResizePinnedStack()");
 
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (animate) {
-                    final ActivityStack stack = mRootActivityContainer.getStack(stackId);
-                    if (stack == null) {
-                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-                        return;
-                    }
-                    if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
-                        throw new IllegalArgumentException("Stack: " + stackId
-                                + " doesn't support animated resize.");
-                    }
-                    stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
-                            animationDuration, false /* fromFullscreen */);
-                } else {
-                    final ActivityStack stack = mRootActivityContainer.getStack(stackId);
-                    if (stack == null) {
-                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-                        return;
-                    }
-                    mRootActivityContainer.resizeStack(stack, destBounds,
-                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                            preserveWindows, allowResizeInDockedMode, !DEFER_RESUME);
+                final ActivityStack stack = mRootActivityContainer.getStack(stackId);
+                if (stack == null) {
+                    Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+                    return;
                 }
+                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    throw new IllegalArgumentException("Stack: " + stackId
+                        + " doesn't support animated resize.");
+                }
+                stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+                        animationDuration, false /* fromFullscreen */);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -2908,7 +2908,7 @@
             mAmInternal.enforceCallingPermission(Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
                     "updateLockTaskPackages()");
         }
-        synchronized (this) {
+        synchronized (mGlobalLock) {
             if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":"
                     + Arrays.toString(packages));
             getLockTaskController().updateLockTaskPackages(userId, packages);
@@ -3237,11 +3237,12 @@
         synchronized (mGlobalLock) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                ActivityRecord r = ActivityRecord.isInStackLocked(token);
-                if (r == null) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null || !r.isDestroyable()) {
                     return false;
                 }
-                return r.safelyDestroy("app-req");
+                r.destroyImmediately(true /* removeFromApp */, "app-req");
+                return r.isState(DESTROYING, DESTROYED);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -3545,6 +3546,11 @@
         return allowed;
     }
 
+    boolean isCrossUserAllowed(int pid, int uid) {
+        return checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED
+                || checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) == PERMISSION_GRANTED;
+    }
+
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
             IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
@@ -4384,17 +4390,19 @@
         mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
         synchronized (mGlobalLock) {
-            if (values == null && mWindowManager != null) {
+            if (mWindowManager == null) {
+                Slog.w(TAG, "Skip updateConfiguration because mWindowManager isn't set");
+                return false;
+            }
+
+            if (values == null) {
                 // sentinel: fetch the current configuration from the window manager
                 values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
             }
 
-            if (mWindowManager != null) {
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
-                        DEFAULT_DISPLAY);
-                mH.sendMessage(msg);
-            }
+            mH.sendMessage(PooledLambda.obtainMessage(
+                    ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal,
+                    DEFAULT_DISPLAY));
 
             final long origId = Binder.clearCallingIdentity();
             try {
@@ -5095,9 +5103,7 @@
         int changes = 0;
         boolean kept = true;
 
-        if (mWindowManager != null) {
-            mWindowManager.deferSurfaceLayout();
-        }
+        deferWindowLayout();
         try {
             if (values != null) {
                 changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId,
@@ -5106,9 +5112,7 @@
 
             kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mWindowManager != null) {
-                mWindowManager.continueSurfaceLayout();
-            }
+            continueWindowLayout();
         }
 
         if (result != null) {
@@ -5236,6 +5240,34 @@
         return changes;
     }
 
+    /** @see WindowSurfacePlacer#deferLayout */
+    void deferWindowLayout() {
+        if (!mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+            // Reset the reasons at the first entrance because we only care about the changes in the
+            // deferred scope.
+            mLayoutReasons = 0;
+        }
+
+        mWindowManager.mWindowPlacerLocked.deferLayout();
+    }
+
+    /** @see WindowSurfacePlacer#continueLayout */
+    void continueWindowLayout() {
+        mWindowManager.mWindowPlacerLocked.continueLayout(mLayoutReasons != 0);
+        if (DEBUG_ALL && !mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
+            Slog.i(TAG, "continueWindowLayout reason=" + mLayoutReasons);
+        }
+    }
+
+    /**
+     * If a reason is added between {@link #deferWindowLayout} and {@link #continueWindowLayout},
+     * it will make sure {@link WindowSurfacePlacer#performSurfacePlacement} is called when the last
+     * defer count is gone.
+     */
+    void addWindowLayoutReasons(@LayoutReason int reasons) {
+        mLayoutReasons |= reasons;
+    }
+
     private void updateEventDispatchingLocked(boolean booted) {
         mWindowManager.setEventDispatching(booted && !mShuttingDown);
     }
@@ -5321,7 +5353,7 @@
         final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 FONT_SCALE, 1.0f, userId);
 
-        synchronized (this) {
+        synchronized (mGlobalLock) {
             if (getGlobalConfiguration().fontScale == scaleFactor) {
                 return;
             }
@@ -5457,7 +5489,7 @@
             updateOomAdj = true;
         }
         if (updateOomAdj) {
-            mH.post(mAmInternal::updateOomAdj);
+            updateOomAdj();
         }
     }
 
@@ -5920,6 +5952,10 @@
         return allUids.contains(uid);
     }
 
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+    }
+
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
 
@@ -6659,7 +6695,7 @@
                 }
 
                 if (!restarting && hasVisibleActivities) {
-                    mWindowManager.deferSurfaceLayout();
+                    deferWindowLayout();
                     try {
                         if (!mRootActivityContainer.resumeFocusedStacksTopActivities()) {
                             // If there was nothing to resume, and we are not already restarting
@@ -6670,7 +6706,7 @@
                                     !PRESERVE_WINDOWS);
                         }
                     } finally {
-                        mWindowManager.continueSurfaceLayout();
+                        continueWindowLayout();
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index be8a0bd..278a9ba 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.annotation.ColorInt;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -41,11 +40,6 @@
     boolean getShowWallpaper();
 
     /**
-     * @return The background color behind the animation.
-     */
-    @ColorInt int getBackgroundColor();
-
-    /**
      * Requests to start the animation.
      *
      * @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 557a609..66d52cc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1999,8 +1999,8 @@
                             mNextAppTransitionFutureCallback, null /* finishedCallback */,
                             mNextAppTransitionScaleUp);
                     mNextAppTransitionFutureCallback = null;
+                    mService.requestTraversal();
                 }
-                mService.requestTraversal();
             });
         }
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 4ceae72..b52ade4 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -41,6 +41,8 @@
 
 import com.android.server.wm.SurfaceAnimator.Animatable;
 
+import java.util.function.Supplier;
+
 /**
  * Represents a surface that is displayed over an {@link AppWindowToken}
  */
@@ -55,8 +57,9 @@
     private final int mHeight;
     private final boolean mRelative;
 
-    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
-        this(t, appToken, thumbnailHeader, false /* relative */);
+    AppWindowThumbnail(Supplier<Surface> surfaceFactory, Transaction t, AppWindowToken appToken,
+            GraphicBuffer thumbnailHeader) {
+        this(surfaceFactory, t, appToken, thumbnailHeader, false /* relative */);
     }
 
     /**
@@ -66,9 +69,9 @@
      * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
      *                 relative to it) or not.
      */
-    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
-            boolean relative) {
-        this(t, appToken, thumbnailHeader, relative, new Surface(), null);
+    AppWindowThumbnail(Supplier<Surface> surfaceFactory, Transaction t, AppWindowToken appToken,
+            GraphicBuffer thumbnailHeader, boolean relative) {
+        this(t, appToken, thumbnailHeader, relative, surfaceFactory.get(), null);
     }
 
     AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 7e0d9a0..f647fe4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -955,6 +955,9 @@
             updateReportedVisibilityLocked();
         }
 
+        // Reset the last saved PiP snap fraction on removal.
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
+
         mRemovingFromDisplay = false;
     }
 
@@ -1021,7 +1024,7 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
         mAppStopped = true;
         // Reset the last saved PiP snap fraction on app stop.
-        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
         destroySurfaces();
         // Remove any starting window that was added for this app if they are still around.
         removeStartingWindow();
@@ -1705,10 +1708,7 @@
             return;
         }
 
-        if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
-            // Entering PiP from fullscreen, reset the snap fraction
-            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
-        } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+        if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
                 && !isHidden()) {
             // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
             // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
@@ -1726,8 +1726,8 @@
                     stackBounds = mTmpRect;
                     pinnedStack.getBounds(stackBounds);
                 }
-                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
-                        stackBounds);
+                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(
+                        mActivityComponent, stackBounds);
             }
         } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
             initializeChangeTransition(mTmpPrevBounds);
@@ -1793,8 +1793,8 @@
                     mWmService.mTaskSnapshotController.createTaskSnapshot(
                             task, 1 /* scaleFraction */);
             if (snapshot != null) {
-                mThumbnail = new AppWindowThumbnail(t, this, snapshot.getGraphicBuffer(),
-                        true /* relative */);
+                mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory, t, this,
+                        snapshot.getGraphicBuffer(), true /* relative */);
             }
         }
     }
@@ -2033,7 +2033,8 @@
         final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent();
         if (needsLetterbox) {
             if (mLetterbox == null) {
-                mLetterbox = new Letterbox(() -> makeChildSurface(null));
+                mLetterbox = new Letterbox(() -> makeChildSurface(null),
+                        mWmService.mTransactionFactory);
                 mLetterbox.attachInput(w);
             }
             getPosition(mTmpPoint);
@@ -2502,14 +2503,18 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
-        // All normal app transitions take place in an animation layer which is below the pinned
-        // stack but may be above the parent stacks of the given animating apps.
         // For transitions in the pinned stack (menu activity) we just let them occur as a child
         // of the pinned stack.
-        if (!inPinnedWindowingMode()) {
-            return getAppAnimationLayer();
-        } else {
+        // All normal app transitions take place in an animation layer which is below the pinned
+        // stack but may be above the parent stacks of the given animating apps by default. When
+        // a new hierarchical animation is enabled, we just let them occur as a child of the parent
+        // stack, i.e. the hierarchy of the surfaces is unchanged.
+        if (inPinnedWindowingMode()) {
             return getStack().getSurfaceControl();
+        } else if (WindowManagerService.sHierarchicalAnimations) {
+            return super.getAnimationLeashParent();
+        } else {
+            return getAppAnimationLayer();
         }
     }
 
@@ -2981,7 +2986,8 @@
             return;
         }
         clearThumbnail();
-        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader);
+        mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory, getPendingTransaction(),
+                this, thumbnailHeader);
         mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
     }
 
@@ -3009,7 +3015,8 @@
         if (thumbnail == null) {
             return;
         }
-        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnail);
+        mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory,
+                getPendingTransaction(), this, thumbnail);
         final Animation animation =
                 getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
                         win.getFrameLw());
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 90bb494..5c4332d 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -27,6 +27,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.WindowManager;
 
 import com.android.server.LocalServices;
@@ -89,6 +90,10 @@
     }
 
     void setWindow(WindowState win) {
+        if (ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
+            // BarController gets replaced with InsetsPolicy in the full insets mode.
+            return;
+        }
         mWin = win;
     }
 
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 7fc17e1..7557271a 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -27,6 +27,7 @@
 import android.view.SurfaceControl;
 
 import java.io.PrintWriter;
+import java.util.function.Supplier;
 
 /**
  * Four black surfaces put together to make a black frame.
@@ -97,7 +98,7 @@
     final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
 
     final boolean mForceDefaultOrientation;
-    private final TransactionFactory mTransactionFactory;
+    private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
 
     public void printTo(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw);
@@ -112,8 +113,8 @@
         }
     }
 
-    public BlackFrame(TransactionFactory factory, SurfaceControl.Transaction t, Rect outer,
-            Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
+    public BlackFrame(Supplier<SurfaceControl.Transaction> factory, SurfaceControl.Transaction t,
+            Rect outer, Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
             throws OutOfResourcesException {
         boolean success = false;
 
@@ -151,7 +152,7 @@
 
     public void kill() {
         if (mBlackSurfaces != null) {
-            SurfaceControl.Transaction t = mTransactionFactory.make();
+            SurfaceControl.Transaction t = mTransactionFactory.get();
             for (int i=0; i<mBlackSurfaces.length; i++) {
                 if (mBlackSurfaces[i] != null) {
                     if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index c3d6211..c1ca816 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -34,6 +34,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class CircularDisplayMask {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
 
@@ -43,7 +45,7 @@
     private Point mScreenSize;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
@@ -53,10 +55,10 @@
     private boolean mDimensionsUnequal = false;
     private int mMaskThickness;
 
-    public CircularDisplayMask(DisplayContent dc, int zOrder,
+    CircularDisplayMask(Supplier<Surface> surfaceFactory, DisplayContent dc, int zOrder,
             int screenOffset, int maskThickness) {
         final Display display = dc.getDisplay();
-
+        mSurface = surfaceFactory.get();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
         if (mScreenSize.x != mScreenSize.y + screenOffset) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 71a0126..63ff2ea 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -100,6 +100,7 @@
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.STACKS;
 import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -549,6 +550,7 @@
     private final PointerEventDispatcher mPointerEventDispatcher;
 
     private final InsetsStateController mInsetsStateController;
+    private final InsetsPolicy mInsetsPolicy;
 
     /** @see #getParentWindow() */
     private WindowState mParentWindow;
@@ -581,27 +583,6 @@
         }
     };
 
-    private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
-        final WindowStateAnimator winAnimator = w.mWinAnimator;
-        if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
-            return;
-        }
-
-        // If this window is animating, ensure the animation background is set.
-        final AnimationAdapter anim = w.mAppToken != null
-                ? w.mAppToken.getAnimation()
-                : w.getAnimation();
-        if (anim != null) {
-            final int color = anim.getBackgroundColor();
-            if (color != 0) {
-                final TaskStack stack = w.getStack();
-                if (stack != null) {
-                    stack.setAnimationBackground(winAnimator, color);
-                }
-            }
-        }
-    };
-
     private final Consumer<WindowState> mScheduleToastTimeout = w -> {
         final int lostFocusUid = mTmpWindow.mOwnerUid;
         final Handler handler = mWmService.mH;
@@ -941,14 +922,17 @@
         final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                 .setOpaque(true)
                 .setContainerLayer();
-        mWindowingLayer = b.setName("Display Root").build();
-        mOverlayLayer = b.setName("Display Overlays").build();
+        mSurfaceControl = b.setName("Root").setContainerLayer().build();
+        mWindowingLayer = b.setName("Display Windows").setParent(mSurfaceControl).build();
+        mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
 
-        getPendingTransaction().setLayer(mWindowingLayer, 0)
-                .setLayerStack(mWindowingLayer, mDisplayId)
+        getPendingTransaction()
+                .setLayer(mSurfaceControl, 0)
+                .setLayerStack(mSurfaceControl, mDisplayId)
+                .show(mSurfaceControl)
+                .setLayer(mWindowingLayer, 0)
                 .show(mWindowingLayer)
                 .setLayer(mOverlayLayer, 1)
-                .setLayerStack(mOverlayLayer, mDisplayId)
                 .show(mOverlayLayer);
         getPendingTransaction().apply();
 
@@ -968,6 +952,7 @@
         mWmService.mAnimator.addDisplayLocked(mDisplayId);
         mInputMonitor = new InputMonitor(service, mDisplayId);
         mInsetsStateController = new InsetsStateController(this);
+        mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
     }
 
     boolean isReady() {
@@ -1054,14 +1039,21 @@
                 // removing from parent.
                 token.getParent().removeChild(token);
             }
-            if (prevDc.mLastFocus == mCurrentFocus) {
-                // The window has become the focus of this display, so it should not be notified
-                // that it lost focus from the previous display.
+            if (token.hasChild(prevDc.mLastFocus)) {
+                // If the reparent window token contains previous display's last focus window, means
+                // it will end up to gain window focus on the target display, so it should not be
+                // notified that it lost focus from the previous display.
                 prevDc.mLastFocus = null;
             }
         }
 
         addWindowToken(token.token, token);
+
+        if (mWmService.mAccessibilityController != null) {
+            final int prevDisplayId = prevDc != null ? prevDc.getDisplayId() : INVALID_DISPLAY;
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(prevDisplayId,
+                    getDisplayId());
+        }
     }
 
     void removeAppToken(IBinder binder) {
@@ -1120,6 +1112,10 @@
         return mInsetsStateController;
     }
 
+    InsetsPolicy getInsetsPolicy() {
+        return mInsetsPolicy;
+    }
+
     @Surface.Rotation
     int getRotation() {
         return mDisplayRotation.getRotation();
@@ -2150,7 +2146,6 @@
      *               so only need to configure display.
      */
     void setForcedDensity(int density, int userId) {
-        final boolean clear = density == mInitialDisplayDensity;
         final boolean updateCurrent = userId == UserHandle.USER_CURRENT;
         if (mWmService.mCurrentUserId == userId || updateCurrent) {
             mBaseDisplayDensity = density;
@@ -2367,12 +2362,6 @@
         mDisplayPolicy.switchUser();
     }
 
-    private void resetAnimationBackgroundAnimator() {
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
-        }
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -2904,9 +2893,11 @@
             mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
         }
 
-        if (DEBUG_FOCUS_LIGHT || mWmService.localLOGV) Slog.v(TAG_WM, "Changing focus from "
-                + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId()
-                + " Callers=" + Debug.getCallers(4));
+        if (DEBUG_FOCUS_LIGHT || DEBUG) {
+            Slog.v(TAG_WM, "Changing focus from "
+                    + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId()
+                    + " Callers=" + Debug.getCallers(4));
+        }
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
         mLosingFocus.remove(newFocus);
@@ -3404,14 +3395,6 @@
         forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
     }
 
-    /**
-     * Updates the {@link TaskStack#setAnimationBackground} for all windows.
-     */
-    void updateBackgroundForAnimator() {
-        resetAnimationBackgroundAnimator();
-        forAllWindows(mUpdateWallpaperForAnimator, true /* traverseTopToBottom */);
-    }
-
     boolean isInputMethodClientFocus(int uid, int pid) {
         final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
@@ -3865,21 +3848,6 @@
         }
     }
 
-    private static final class ScreenshotApplicationState {
-        WindowState appWin;
-        int maxLayer;
-        int minLayer;
-        boolean screenshotReady;
-
-        void reset(boolean screenshotReady) {
-            appWin = null;
-            maxLayer = 0;
-            minLayer = 0;
-            this.screenshotReady = screenshotReady;
-            minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE;
-        }
-    }
-
     /**
      * Base class for any direct child window container of {@link #DisplayContent} need to inherit
      * from. This is mainly a pass through class that allows {@link #DisplayContent} to have
@@ -4388,7 +4356,7 @@
                         .show(mSplitScreenDividerAnchor);
                 scheduleAnimation();
             } else {
-                mWmService.mTransactionFactory.make()
+                mWmService.mTransactionFactory.get()
                         .remove(mAppAnimationLayer)
                         .remove(mBoostedAppAnimationLayer)
                         .remove(mHomeAppAnimationLayer)
@@ -4867,7 +4835,7 @@
             mPortalWindowHandle = createPortalWindowHandle(sc.toString());
         }
         getPendingTransaction().setInputWindowInfo(sc, mPortalWindowHandle)
-                .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc);
+                .reparent(mSurfaceControl, sc);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8328770..10d48c4 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -103,7 +103,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.localLOGV;
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
@@ -336,7 +335,6 @@
 
     private static final Rect sTmpDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
     private static final Rect sTmpRect = new Rect();
-    private static final Rect sTmpDockedFrame = new Rect();
     private static final Rect sTmpNavFrame = new Rect();
     private static final Rect sTmpLastParentFrame = new Rect();
 
@@ -1029,6 +1027,14 @@
                 displayFrames.mDisplayCutoutSafe.top);
     }
 
+    WindowState getStatusBar() {
+        return mStatusBar;
+    }
+
+    WindowState getNavigationBar() {
+        return mNavigationBar;
+    }
+
     /**
      * Control the animation to run when a window's state changes.  Return a
      * non-0 number to force the animation to a specific resource ID, or 0
@@ -2589,7 +2595,7 @@
         }
         final int fl = PolicyControl.getWindowFlags(null,
                 mTopFullscreenOpaqueWindowState.getAttrs());
-        if (localLOGV) {
+        if (WindowManagerDebugConfig.DEBUG) {
             Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
             Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
                     + " lp.flags=0x" + Integer.toHexString(fl));
@@ -3108,8 +3114,7 @@
             return 0;
         }
 
-        mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(
-                mTopFullscreenOpaqueWindowState);
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
         int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
                 & ~mResettingSystemUiFlags
@@ -3544,7 +3549,8 @@
         if (mScreenshotHelper != null) {
             mScreenshotHelper.takeScreenshot(screenshotType,
                     mStatusBar != null && mStatusBar.isVisibleLw(),
-                    mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
+                    mNavigationBar != null && mNavigationBar.isVisibleLw(),
+                    mHandler, null /* completionConsumer */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 120ce3e..ae3b5f2 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -36,7 +36,6 @@
 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -141,8 +140,6 @@
     float mLastDividerProgress;
     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
     private boolean mImeHideRequested;
-    private final Rect mLastDimLayerRect = new Rect();
-    private float mLastDimLayerAlpha;
     private TaskStack mDimmedStack;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
@@ -656,14 +653,6 @@
     }
 
     /**
-     * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
-     *         above all application surfaces.
-     */
-    private int getResizeDimLayer() {
-        return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
-    }
-
-    /**
      * Notifies the docked stack divider controller of a visibility change that happens without
      * an animation.
      */
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 20a1333..c5c2364 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.UriGrantsManager;
 import android.content.ClipData;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c48f07c..17daabf 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -140,7 +140,7 @@
         mFlags = flags;
         mLocalWin = localWin;
         mNotifiedWindows = new ArrayList<WindowState>();
-        mTransaction = service.mTransactionFactory.make();
+        mTransaction = service.mTransactionFactory.get();
     }
 
     boolean isClosing() {
@@ -695,7 +695,8 @@
             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
-            try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+            try (SurfaceControl.Transaction transaction =
+                         mService.mTransactionFactory.get()) {
                 transaction.setPosition(
                         mSurfaceControl,
                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 7cb4a43..f64592f 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -32,6 +32,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class EmulatorDisplayOverlay {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmulatorDisplayOverlay" : TAG_WM;
 
@@ -39,7 +41,7 @@
     private Point mScreenSize;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
@@ -47,8 +49,9 @@
     private int mRotation;
     private boolean mVisible;
 
-    public EmulatorDisplayOverlay(Context context, DisplayContent dc,
+    EmulatorDisplayOverlay(Supplier<Surface> surfaceFactory, Context context, DisplayContent dc,
             int zOrder) {
+        mSurface = surfaceFactory.get();
         final Display display = dc.getDisplay();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index 315de91..8ae740d 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,9 +58,9 @@
     @VisibleForTesting
     HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
         mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
-        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 BackgroundThread.getExecutor(), new OnPropertiesChangedListener());
-        final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+        final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 KEY_HIGH_REFRESH_RATE_BLACKLIST);
         updateBlacklist(property);
     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 6830ade..ec36a82 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -181,9 +181,8 @@
      */
     @Override
     public long interceptKeyBeforeDispatching(
-            IBinder focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = mService.windowForClientLocked(null, focus, false);
-        return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
+            IBinder focusedToken, KeyEvent event, int policyFlags) {
+        return mService.mPolicy.interceptKeyBeforeDispatching(focusedToken, event, policyFlags);
     }
 
     /**
@@ -192,9 +191,8 @@
      */
     @Override
     public KeyEvent dispatchUnhandledKey(
-            IBinder focus, KeyEvent event, int policyFlags) {
-        WindowState windowState = mService.windowForClientLocked(null, focus, false);
-        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
+            IBinder focusedToken, KeyEvent event, int policyFlags) {
+        return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags);
     }
 
     /** Callback to get pointer layer. */
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 2eec926..8e0531c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -153,7 +153,7 @@
         mService = service;
         mDisplayContent = mService.mRoot.getDisplayContent(displayId);
         mDisplayId = displayId;
-        mInputTransaction = mService.mTransactionFactory.make();
+        mInputTransaction = mService.mTransactionFactory.get();
         mHandler = AnimationThread.getHandler();
         mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
     }
@@ -528,6 +528,10 @@
             populateInputWindowHandle(
                     inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
 
+            // register key interception info
+            mService.mKeyInterceptionInfoForToken.put(inputWindowHandle.token,
+                    w.getKeyInterceptionInfo());
+
             if (w.mWinAnimator.hasSurface()) {
                 mInputTransaction.setInputWindowInfo(
                         w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
new file mode 100644
index 0000000..3db6dcf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+/**
+ * Generalization of an object that can control insets state.
+ */
+interface InsetsControlTarget {
+    void notifyInsetsControlChanged();
+}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
new file mode 100644
index 0000000..2dc50d8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+
+import android.annotation.Nullable;
+
+/**
+ * Policy that implements who gets control over the windows generating insets.
+ */
+class InsetsPolicy {
+
+    private final InsetsStateController mStateController;
+    private final DisplayContent mDisplayContent;
+    private final DisplayPolicy mPolicy;
+
+    InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
+        mStateController = stateController;
+        mDisplayContent = displayContent;
+        mPolicy = displayContent.getDisplayPolicy();
+    }
+
+    /** Updates the target which can control system bars. */
+    void updateBarControlTarget(@Nullable WindowState focusedWin) {
+        mStateController.onBarControlTargetChanged(getTopControlTarget(focusedWin),
+                getNavControlTarget(focusedWin));
+    }
+
+    private @Nullable InsetsControlTarget getTopControlTarget(@Nullable WindowState focusedWin) {
+        if (areSystemBarsForciblyVisible() || isStatusBarForciblyVisible()) {
+            return null;
+        }
+        return focusedWin;
+    }
+
+    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin) {
+        if (areSystemBarsForciblyVisible() || isNavBarForciblyVisible()) {
+            return null;
+        }
+        return focusedWin;
+    }
+
+    private boolean isStatusBarForciblyVisible() {
+        final WindowState statusBar = mPolicy.getStatusBar();
+        if (statusBar == null) {
+            return false;
+        }
+        final int privateFlags = statusBar.mAttrs.privateFlags;
+
+        // TODO: Pretend to the app that it's still able to control it?
+        if ((privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) {
+            return true;
+        }
+        if ((privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isNavBarForciblyVisible() {
+        final WindowState statusBar = mPolicy.getStatusBar();
+        if (statusBar == null) {
+            return false;
+        }
+        if ((statusBar.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean areSystemBarsForciblyVisible() {
+        final boolean isDockedStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final boolean isFreeformStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM);
+        final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing();
+
+        // We need to force system bars when the docked stack is visible, when the freeform stack
+        // is visible but also when we are resizing for the transitions when docked stack
+        // visibility changes.
+        return isDockedStackVisible || isFreeformStackVisible || isResizing;
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b7835aa..8426864 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -51,8 +51,11 @@
     private final @NonNull InsetsSource mSource;
     private final DisplayContent mDisplayContent;
     private final InsetsStateController mStateController;
+    private final InsetsSourceControl mFakeControl;
     private @Nullable InsetsSourceControl mControl;
-    private @Nullable WindowState mControllingWin;
+    private @Nullable InsetsControlTarget mControlTarget;
+    private @Nullable InsetsControlTarget mFakeControlTarget;
+
     private @Nullable ControlAdapter mAdapter;
     private WindowState mWin;
     private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
@@ -73,6 +76,8 @@
         mSource = source;
         mDisplayContent = displayContent;
         mStateController = stateController;
+        mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */,
+                new Point());
 
         final int type = source.getType();
         if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
@@ -119,8 +124,8 @@
             mSource.setFrame(new Rect());
         } else {
             mWin.setInsetProvider(this);
-            if (mControllingWin != null) {
-                updateControlForTarget(mControllingWin, true /* force */);
+            if (mControlTarget != null) {
+                updateControlForTarget(mControlTarget, true /* force */);
             }
         }
     }
@@ -143,19 +148,29 @@
         if (mControl != null) {
             final Rect frame = mWin.getWindowFrames().mFrame;
             if (mControl.setSurfacePosition(frame.left, frame.top)) {
-                mStateController.notifyControlChanged(mControllingWin);
+                mStateController.notifyControlChanged(mControlTarget);
             }
         }
         setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy()
                 && !mWin.mGivenInsetsPending);
     }
 
-    void updateControlForTarget(@Nullable WindowState target, boolean force) {
-        if (mWin == null) {
-            mControllingWin = target;
+    /**
+     * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
+     */
+    void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
+        if (fakeTarget == mFakeControlTarget) {
             return;
         }
-        if (target == mControllingWin && !force) {
+        mFakeControlTarget = fakeTarget;
+    }
+
+    void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+        if (mWin == null) {
+            mControlTarget = target;
+            return;
+        }
+        if (target == mControlTarget && !force) {
             return;
         }
         if (target == null) {
@@ -167,13 +182,13 @@
         setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
         mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
                 !mClientVisible /* hidden */);
-        mControllingWin = target;
+        mControlTarget = target;
         mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
                 new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
     }
 
     boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
-        if (mControllingWin != caller || modifiedSource.isVisible() == mClientVisible) {
+        if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
             return false;
         }
         setClientVisible(modifiedSource.isVisible());
@@ -199,8 +214,14 @@
         mSource.setVisible(mServerVisible && mClientVisible);
     }
 
-    InsetsSourceControl getControl() {
-        return mControl;
+    InsetsSourceControl getControl(InsetsControlTarget target) {
+        if (target == mControlTarget) {
+            return mControl;
+        }
+        if (target == mFakeControlTarget) {
+            return mFakeControl;
+        }
+        return null;
     }
 
     boolean isClientVisible() {
@@ -217,11 +238,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             mCapturedLeash = animationLeash;
@@ -232,10 +248,10 @@
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
             if (mAdapter == this) {
-                mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
+                mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
                 setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
                 mControl = null;
-                mControllingWin = null;
+                mControlTarget = null;
                 mAdapter = null;
             }
         }
@@ -257,5 +273,5 @@
         @Override
         public void writeToProto(ProtoOutputStream proto) {
         }
-    };
+    }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index a1b52f4..4ebb553 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -16,11 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.InternalInsetType;
 import static android.view.InsetsState.TYPE_IME;
 import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.TYPE_TOP_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 
 import android.annotation.NonNull;
@@ -31,7 +31,6 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.ViewRootImpl;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -46,10 +45,15 @@
     private final InsetsState mState = new InsetsState();
     private final DisplayContent mDisplayContent;
 
-    private final ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>();
-    private final ArrayMap<WindowState, ArrayList<Integer>> mWinControlTypeMap = new ArrayMap<>();
-    private final SparseArray<WindowState> mTypeWinControlMap = new SparseArray<>();
-    private final ArraySet<WindowState> mPendingControlChanged = new ArraySet<>();
+    private final ArrayMap<Integer, InsetsSourceProvider> mProviders = new ArrayMap<>();
+    private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap =
+            new ArrayMap<>();
+    private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>();
+
+    /** @see #onControlFakeTargetChanged */
+    private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>();
+
+    private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
 
     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
         if (w.isVisible()) {
@@ -87,15 +91,15 @@
         return state;
     }
 
-    @Nullable InsetsSourceControl[] getControlsForDispatch(WindowState target) {
-        ArrayList<Integer> controlled = mWinControlTypeMap.get(target);
+    @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
+        ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
         if (controlled == null) {
             return null;
         }
         final int size = controlled.size();
         final InsetsSourceControl[] result = new InsetsSourceControl[size];
         for (int i = 0; i < size; i++) {
-            result[i] = mControllers.get(controlled.get(i)).getControl();
+            result[i] = mProviders.get(controlled.get(i)).getControl(target);
         }
         return result;
     }
@@ -103,8 +107,8 @@
     /**
      * @return The provider of a specific type.
      */
-    InsetsSourceProvider getSourceProvider(int type) {
-        return mControllers.computeIfAbsent(type,
+    InsetsSourceProvider getSourceProvider(@InternalInsetType int type) {
+        return mProviders.computeIfAbsent(type,
                 key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent));
     }
 
@@ -113,8 +117,8 @@
      */
     void onPostLayout() {
         mState.setDisplayFrame(mDisplayContent.getBounds());
-        for (int i = mControllers.size() - 1; i>= 0; i--) {
-            mControllers.valueAt(i).onPostLayout();
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            mProviders.valueAt(i).onPostLayout();
         }
         if (!mLastState.equals(mState)) {
             mLastState.set(mState, true /* copySources */);
@@ -126,7 +130,7 @@
         boolean changed = false;
         for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
-            final InsetsSourceProvider provider = mControllers.get(source.getType());
+            final InsetsSourceProvider provider = mProviders.get(source.getType());
             if (provider == null) {
                 continue;
             }
@@ -137,75 +141,116 @@
         }
     }
 
-    void onImeTargetChanged(@Nullable WindowState imeTarget) {
+    void onImeTargetChanged(@Nullable InsetsControlTarget imeTarget) {
         onControlChanged(TYPE_IME, imeTarget);
         notifyPendingInsetsControlChanged();
     }
 
     /**
-     * Called when the top opaque fullscreen window that is able to control the system bars changes.
+     * Called when the focused window that is able to control the system bars changes.
      *
-     * @param controllingWindow The window that is now able to control the system bars appearance
-     *                          and visibility.
+     * @param topControlling The target that is now able to control the top bar appearance
+     *                       and visibility.
+     * @param navControlling The target that is now able to control the nav bar appearance
+     *                       and visibility.
      */
-    void onBarControllingWindowChanged(@Nullable WindowState controllingWindow) {
-        // TODO: Apply policy that determines whether controllingWindow is able to control system
-        // bars
-
-        // TODO: Depending on the form factor, mapping is different
-        onControlChanged(TYPE_TOP_BAR, controllingWindow);
-        onControlChanged(TYPE_NAVIGATION_BAR, controllingWindow);
+    void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling,
+            @Nullable InsetsControlTarget navControlling) {
+        onControlChanged(TYPE_TOP_BAR, topControlling);
+        onControlChanged(TYPE_NAVIGATION_BAR, navControlling);
         notifyPendingInsetsControlChanged();
     }
 
-    void notifyControlRevoked(@NonNull WindowState previousControllingWin,
+    void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
-        removeFromControlMaps(previousControllingWin, provider.getSource().getType());
+        removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
+                false /* fake */);
     }
 
-    private void onControlChanged(int type, @Nullable WindowState win) {
-        final WindowState previous = mTypeWinControlMap.get(type);
-        if (win == previous) {
+    private void onControlChanged(@InternalInsetType int type,
+            @Nullable InsetsControlTarget target) {
+        final InsetsControlTarget previous = mTypeControlTargetMap.get(type);
+        if (target == previous) {
             return;
         }
-        final InsetsSourceProvider controller = getSourceProvider(type);
-        if (controller == null) {
+        final InsetsSourceProvider provider = getSourceProvider(type);
+        if (provider == null) {
             return;
         }
-        if (!controller.isControllable()) {
+        if (!provider.isControllable()) {
             return;
         }
-        controller.updateControlForTarget(win, false /* force */);
+        provider.updateControlForTarget(target, false /* force */);
         if (previous != null) {
-            removeFromControlMaps(previous, type);
+            removeFromControlMaps(previous, type, false /* fake */);
             mPendingControlChanged.add(previous);
         }
-        if (win != null) {
-            addToControlMaps(win, type);
-            mPendingControlChanged.add(win);
+        if (target != null) {
+            addToControlMaps(target, type, false /* fake */);
+            mPendingControlChanged.add(target);
         }
     }
 
-    private void removeFromControlMaps(@NonNull WindowState win, int type) {
-        final ArrayList<Integer> array = mWinControlTypeMap.get(win);
+    /**
+     * The fake target saved here will be used to pretend to the app that it's still under control
+     * of the bars while it's not really, but we still need to find out the apps intentions around
+     * showing/hiding. For example, when the transient bars are showing, and the fake target
+     * requests to show system bars, the transient state will be aborted.
+     */
+    void onControlFakeTargetChanged(@InternalInsetType  int type,
+            @Nullable InsetsControlTarget fakeTarget) {
+        if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+            return;
+        }
+        final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type);
+        if (fakeTarget == previous) {
+            return;
+        }
+        final InsetsSourceProvider provider = mProviders.get(type);
+        if (provider == null) {
+            return;
+        }
+        provider.updateControlForFakeTarget(fakeTarget);
+        if (previous != null) {
+            removeFromControlMaps(previous, type, true /* fake */);
+            mPendingControlChanged.add(previous);
+        }
+        if (fakeTarget != null) {
+            addToControlMaps(fakeTarget, type, true /* fake */);
+            mPendingControlChanged.add(fakeTarget);
+        }
+    }
+
+    private void removeFromControlMaps(@NonNull InsetsControlTarget target,
+            @InternalInsetType int type, boolean fake) {
+        final ArrayList<Integer> array = mControlTargetTypeMap.get(target);
         if (array == null) {
             return;
         }
         array.remove((Integer) type);
         if (array.isEmpty()) {
-            mWinControlTypeMap.remove(win);
+            mControlTargetTypeMap.remove(target);
         }
-        mTypeWinControlMap.remove(type);
+        if (fake) {
+            mTypeFakeControlTargetMap.remove(type);
+        } else {
+            mTypeControlTargetMap.remove(type);
+        }
     }
 
-    private void addToControlMaps(@NonNull WindowState win, int type) {
-        final ArrayList<Integer> array = mWinControlTypeMap.computeIfAbsent(win,
+    private void addToControlMaps(@NonNull InsetsControlTarget target,
+            @InternalInsetType int type, boolean fake) {
+        final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target,
                 key -> new ArrayList<>());
         array.add(type);
-        mTypeWinControlMap.put(type, win);
+        if (fake) {
+            mTypeFakeControlTargetMap.put(type, target);
+        } else {
+            mTypeControlTargetMap.put(type, target);
+        }
     }
 
-    void notifyControlChanged(WindowState target) {
+    void notifyControlChanged(InsetsControlTarget target) {
         mPendingControlChanged.add(target);
         notifyPendingInsetsControlChanged();
     }
@@ -216,8 +261,8 @@
         }
         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
-                final WindowState controllingWin = mPendingControlChanged.valueAt(i);
-                controllingWin.notifyInsetsControlChanged();
+                final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
+                controlTarget.notifyInsetsControlChanged();
             }
             mPendingControlChanged.clear();
         });
@@ -231,10 +276,10 @@
         pw.println(prefix + "WindowInsetsStateController");
         mState.dump(prefix + "  ", pw);
         pw.println(prefix + "  " + "Control map:");
-        for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
+        for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
             pw.print(prefix + "  ");
-            pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
-                    + mTypeWinControlMap.valueAt(i));
+            pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
+                    + mTypeControlTargetMap.valueAt(i));
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 422b6e5..2b5eb3a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -71,7 +71,6 @@
     private boolean mAodShowing;
     private boolean mKeyguardGoingAway;
     private boolean mDismissalRequested;
-    private int[] mSecondaryDisplayIdsShowing;
     private int mBeforeUnoccludeTransit;
     private int mVisibilityTransactionDepth;
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
@@ -182,7 +181,7 @@
             return;
         }
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             setKeyguardGoingAway(true);
             EventLog.writeEvent(EventLogTags.AM_SET_KEYGUARD_SHOWN,
@@ -204,7 +203,7 @@
             mWindowManager.executeAppTransition();
         } finally {
             Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
 
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -328,9 +327,9 @@
             return;
         }
 
-        mWindowManager.onKeyguardOccludedChanged(isDisplayOccluded(DEFAULT_DISPLAY));
+        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
         if (isKeyguardLocked()) {
-            mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             try {
                 mRootActivityContainer.getDefaultDisplay().mDisplayContent
                         .prepareAppTransition(resolveOccludeTransit(),
@@ -340,7 +339,7 @@
                 mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
                 mWindowManager.executeAppTransition();
             } finally {
-                mWindowManager.continueSurfaceLayout();
+                mService.continueWindowLayout();
             }
         }
         dismissDockedStackIfNeeded();
@@ -381,7 +380,7 @@
      * @return true if Keyguard can be currently dismissed without entering credentials.
      */
     boolean canDismissKeyguard() {
-        return mWindowManager.isKeyguardTrusted()
+        return mWindowManager.mPolicy.isKeyguardTrustedLw()
                 || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
     }
 
@@ -516,7 +515,8 @@
             }
             // TODO(b/123372519): isShowingDream can only works on default display.
             if (mDisplayId == DEFAULT_DISPLAY) {
-                mOccluded |= controller.mWindowManager.isShowingDream();
+                mOccluded |= mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent
+                        .getDisplayPolicy().isShowingDreamLw();
             }
 
             if (lastOccluded != mOccluded) {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 59c02f7..6de48d1 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -133,7 +133,7 @@
             return false;
         }
 
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (mTmpParams.hasPreferredDisplay()
@@ -152,7 +152,7 @@
 
             if (task.getStack().inFreeformWindowingMode()) {
                 // Only set bounds if it's in freeform mode.
-                task.updateOverrideConfiguration(mTmpParams.mBounds);
+                task.setBounds(mTmpParams.mBounds);
                 return true;
             }
 
@@ -161,7 +161,7 @@
             task.setLastNonFullscreenBounds(mTmpParams.mBounds);
             return false;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index bb035d5..1bd2493 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -41,7 +41,8 @@
     private static final Rect EMPTY_RECT = new Rect();
     private static final Point ZERO_POINT = new Point(0, 0);
 
-    private final Supplier<SurfaceControl.Builder> mFactory;
+    private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
+    private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
     private final Rect mOuter = new Rect();
     private final Rect mInner = new Rect();
     private final LetterboxSurface mTop = new LetterboxSurface("top");
@@ -55,8 +56,10 @@
      *
      * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s
      */
-    public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) {
-        mFactory = surfaceControlFactory;
+    public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            Supplier<SurfaceControl.Transaction> transactionFactory) {
+        mSurfaceControlFactory = surfaceControlFactory;
+        mTransactionFactory = transactionFactory;
     }
 
     /**
@@ -245,7 +248,7 @@
         }
 
         private void createSurface() {
-            mSurface = mFactory.get().setName("Letterbox - " + mType)
+            mSurface = mSurfaceControlFactory.get().setName("Letterbox - " + mType)
                     .setFlags(HIDDEN).setColorLayer().build();
             mSurface.setLayer(-1);
             mSurface.setColor(new float[]{0, 0, 0});
@@ -261,7 +264,7 @@
 
         public void remove() {
             if (mSurface != null) {
-                new SurfaceControl.Transaction().remove(mSurface).apply();
+                mTransactionFactory.get().remove(mSurface).apply();
                 mSurface = null;
             }
             if (mInputInterceptor != null) {
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 77a024c..e67cb6fc 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -49,11 +49,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return mSpec.getBackgroundColor();
-    }
-
-    @Override
     public void startAnimation(SurfaceControl animationLeash, Transaction t,
             OnAnimationFinishedCallback finishCallback) {
         mAnimator.startAnimation(mSpec, animationLeash, t,
@@ -100,13 +95,6 @@
         }
 
         /**
-         * @see AnimationAdapter#getBackgroundColor
-         */
-        default int getBackgroundColor() {
-            return 0;
-        }
-
-        /**
          * @see AnimationAdapter#getStatusBarTransitionsStartTime
          */
         default long calculateStatusBarTransitionStartTime() {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index ef0049b..8e57fec 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -50,7 +51,6 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -74,7 +74,7 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
 
-    public static final float INVALID_SNAP_FRACTION = -1f;
+    private static final float INVALID_SNAP_FRACTION = -1f;
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler = UiThread.getHandler();
@@ -106,9 +106,6 @@
     private int mDefaultStackGravity;
     private float mDefaultAspectRatio;
     private Point mScreenEdgeInsets;
-    private int mCurrentMinSize;
-    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private WeakReference<AppWindowToken> mLastPipActivity = null;
 
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
@@ -118,7 +115,6 @@
     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
     private final Rect mTmpInsets = new Rect();
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpAnimatingBoundsRect = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
 
@@ -136,18 +132,21 @@
         }
 
         @Override
-        public void setMinEdgeSize(int minEdgeSize) {
-            mHandler.post(() -> {
-                mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
-            });
-        }
-
-        @Override
         public int getDisplayRotation() {
             synchronized (mService.mGlobalLock) {
                 return mDisplayInfo.rotation;
             }
         }
+
+        @Override
+        public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
+                int animationDuration) {
+            synchronized (mService.mGlobalLock) {
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+                pinnedStack.animateResizePinnedStack(destinationBounds,
+                        sourceRectHint, animationDuration, true /* fromFullscreen */);
+            }
+        }
     }
 
     /**
@@ -188,7 +187,6 @@
         final Resources res = mService.mContext.getResources();
         mDefaultMinSize = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
-        mCurrentMinSize = mDefaultMinSize;
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
         final String screenEdgeInsetsDpString = res.getString(
@@ -216,6 +214,7 @@
             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
+            notifyDisplayInfoChanged(mDisplayInfo);
             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
             notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
             // The movement bounds notification needs to be sent before the minimized state, since
@@ -238,58 +237,34 @@
     }
 
     /**
-     * Returns the current bounds (or the default bounds if there are no current bounds) with the
-     * specified aspect ratio.
-     */
-    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
-            boolean useCurrentMinEdgeSize) {
-        // Save the snap fraction, calculate the aspect ratio based on screen size
-        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
-                getMovementBounds(stackBounds));
-
-        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
-        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
-                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
-        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
-        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
-        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
-        if (mIsMinimized) {
-            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
-        }
-        return stackBounds;
-    }
-
-    /**
      * Saves the current snap fraction for re-entry of the current activity into PiP.
      */
-    void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipActivity = new WeakReference<>(token);
+    void saveReentrySnapFraction(final ComponentName componentName, final Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onSaveReentrySnapFraction(componentName, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering save reentry fraction event.", e);
+        }
     }
 
     /**
      * Resets the last saved snap fraction so that the default bounds will be returned.
      */
-    void resetReentrySnapFraction(AppWindowToken token) {
-        if (mLastPipActivity != null && mLastPipActivity.get() == token) {
-            mReentrySnapFraction = INVALID_SNAP_FRACTION;
-            mLastPipActivity = null;
+    void resetReentrySnapFraction(ComponentName componentName) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onResetReentrySnapFraction(componentName);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
         }
     }
 
     /**
-     * @return the default bounds to show the PIP when there is no active PIP.
-     */
-    Rect getDefaultOrLastSavedBounds() {
-        return getDefaultBounds(mReentrySnapFraction);
-    }
-
-    /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
      * will apply the default bounds to the provided snap fraction.
      */
-    Rect getDefaultBounds(float snapFraction) {
+    private Rect getDefaultBounds(float snapFraction) {
         synchronized (mService.mGlobalLock) {
             final Rect insetBounds = new Rect();
             getInsetBounds(insetBounds);
@@ -311,13 +286,18 @@
         }
     }
 
+    private void setDisplayInfo(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+        notifyDisplayInfoChanged(mDisplayInfo);
+    }
+
     /**
      * In the case where the display rotation is changed but there is no stack, we can't depend on
      * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
      * with the new state so that we can update SystemUI.
      */
     synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
-        mDisplayInfo.copyFrom(displayInfo);
+        setDisplayInfo(displayInfo);
         notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
     }
 
@@ -335,7 +315,7 @@
             } else if (targetBounds.isEmpty()) {
                 // The stack is null, we are just initializing the stack, so just store the display
                 // info and ignore
-                mDisplayInfo.copyFrom(displayInfo);
+                setDisplayInfo(displayInfo);
                 outBounds.setEmpty();
                 return false;
             }
@@ -345,7 +325,8 @@
 
             // Calculate the snap fraction of the current stack along the old movement bounds
             final float snapFraction = getSnapFraction(postChangeStackBounds);
-            mDisplayInfo.copyFrom(displayInfo);
+
+            setDisplayInfo(displayInfo);
 
             // Calculate the stack bounds in the new orientation to the same same fraction along the
             // rotated movement bounds.
@@ -406,8 +387,11 @@
     void setAspectRatio(float aspectRatio) {
         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
             mAspectRatio = aspectRatio;
+            notifyAspectRatioChanged(aspectRatio);
             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
                     false /* fromShelfAdjustment */);
+            notifyPrepareAnimation(null /* sourceHintRect */, aspectRatio,
+                    null /* stackBounds */);
         }
     }
 
@@ -429,6 +413,10 @@
         notifyActionsChanged(mActions);
     }
 
+    void prepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+    }
+
     private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
             @NonNull DisplayInfo display2) {
         Preconditions.checkNotNull(display1);
@@ -461,6 +449,15 @@
         }
     }
 
+    private void notifyAspectRatioChanged(float aspectRatio) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onAspectRatioChanged(aspectRatio);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
+        }
+    }
+
     /**
      * Notifies listeners that the PIP minimized state has changed.
      */
@@ -497,23 +494,13 @@
                 return;
             }
             try {
-                final Rect insetBounds = new Rect();
-                getInsetBounds(insetBounds);
-                final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
-                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                            false /* useCurrentMinEdgeSize */);
-                }
-                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final Rect animatingBounds = new Rect();
                 final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                } else {
-                    animatingBounds.set(normalBounds);
                 }
-                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
-                        mDisplayInfo.rotation);
+                mPinnedStackListener.onMovementBoundsChanged(animatingBounds,
+                        fromImeAdjustment, fromShelfAdjustment);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
@@ -521,6 +508,30 @@
     }
 
     /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyDisplayInfoChanged(DisplayInfo displayInfo) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onDisplayInfoChanged(displayInfo);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering DisplayInfo changed event.", e);
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering prepare animation event.", e);
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -604,7 +615,6 @@
         pw.println(prefix + "  mImeHeight=" + mImeHeight);
         pw.println(prefix + "  mIsShelfShowing=" + mIsShelfShowing);
         pw.println(prefix + "  mShelfHeight=" + mShelfHeight);
-        pw.println(prefix + "  mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
index 8d08aa3..6b8144c 100644
--- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -28,13 +28,11 @@
 import java.util.ArrayList;
 
 public class PointerEventDispatcher extends InputEventReceiver {
-    private final InputChannel mInputChannel;
     private final ArrayList<PointerEventListener> mListeners = new ArrayList<>();
     private PointerEventListener[] mListenersArray = new PointerEventListener[0];
 
     public PointerEventDispatcher(InputChannel inputChannel) {
         super(inputChannel, UiThread.getHandler().getLooper());
-        mInputChannel = inputChannel;
     }
 
     @Override
@@ -94,7 +92,6 @@
     @Override
     public void dispose() {
         super.dispose();
-        mInputChannel.dispose();
         synchronized (mListeners) {
             mListeners.clear();
             mListenersArray = null;
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/services/core/java/com/android/server/wm/ProtoLogGroup.java
new file mode 100644
index 0000000..313cceb
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ProtoLogGroup.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.ProtoLog;
+
+/**
+ * Defines logging groups for ProtoLog.
+ *
+ * This file is used by the ProtoLogTool to generate optimized logging code. All of its dependencies
+ * must be included in services.core.wm.protologgroups build target.
+ */
+public enum ProtoLogGroup implements IProtoLogGroup {
+    GENERIC_WM(true, true, false, "WindowManager"),
+
+    TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+    private final boolean mEnabled;
+    private volatile boolean mLogToProto;
+    private volatile boolean mLogToLogcat;
+    private final String mTag;
+
+    /**
+     * @param enabled     set to false to exclude all log statements for this group from
+     *                    compilation,
+     *                    they will not be available in runtime.
+     * @param logToProto  enable binary logging for the group
+     * @param logToLogcat enable text logging for the group
+     * @param tag         name of the source of the logged message
+     */
+    ProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+        this.mEnabled = enabled;
+        this.mLogToProto = logToProto;
+        this.mLogToLogcat = logToLogcat;
+        this.mTag = tag;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    @Override
+    public boolean isLogToProto() {
+        return mLogToProto;
+    }
+
+    @Override
+    public boolean isLogToLogcat() {
+        return mLogToLogcat;
+    }
+
+    @Override
+    public boolean isLogToAny() {
+        return mLogToLogcat || mLogToProto;
+    }
+
+    @Override
+    public String getTag() {
+        return mTag;
+    }
+
+    @Override
+    public void setLogToProto(boolean logToProto) {
+        this.mLogToProto = logToProto;
+    }
+
+    @Override
+    public void setLogToLogcat(boolean logToLogcat) {
+        this.mLogToLogcat = logToLogcat;
+    }
+
+    /**
+     * Test function for automated integration tests. Can be also called manually from adb shell.
+     */
+    @VisibleForTesting
+    public static void testProtoLog() {
+        ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+                "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+                true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 1a8944a..5cabbd9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -196,7 +196,7 @@
             mCaller.setRunningRecentsAnimation(true);
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
@@ -260,7 +260,7 @@
             Slog.e(TAG, "Failed to start recents activity", e);
             throw e;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -297,7 +297,7 @@
             mWindowManager.inSurfaceTransaction(() -> {
                 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
-                mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     mWindowManager.cleanupRecentsAnimation(reorderMode);
 
@@ -387,7 +387,7 @@
                     Slog.e(TAG, "Failed to clean up recents activity", e);
                     throw e;
                 } finally {
-                    mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                     Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
                 }
             });
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 8752f37..795a2ca 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -93,6 +93,8 @@
     private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final int mDisplayId;
     private final Runnable mFailsafeRunnable = () ->
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
@@ -434,6 +436,13 @@
         mPendingAnimations.remove(taskAdapter);
     }
 
+    @VisibleForTesting
+    void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeWallpaperAnimation()");
+        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter);
+        mPendingWallpaperAnimations.remove(wallpaperAdapter);
+    }
+
     void startAnimation() {
         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
                 + " mCanceled=" + mCanceled);
@@ -442,25 +451,18 @@
             return;
         }
         try {
-            final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>();
-            for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-                final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp();
-                if (target != null) {
-                    appAnimations.add(target);
-                } else {
-                    removeAnimation(taskAdapter);
-                }
-            }
+            // Create the app targets
+            final RemoteAnimationTarget[] appTargets = createAppAnimations();
 
             // Skip the animation if there is nothing to animate
-            if (appAnimations.isEmpty()) {
+            if (appTargets.length == 0) {
                 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
                 return;
             }
 
-            final RemoteAnimationTarget[] appTargets = appAnimations.toArray(
-                    new RemoteAnimationTarget[appAnimations.size()]);
+            // Create the wallpaper targets
+            final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
+
             mPendingStart = false;
 
             // Perform layout if it was scheduled before to make sure that we get correct content
@@ -479,7 +481,8 @@
                 mService.getStableInsets(mDisplayId, mTmpRect);
                 contentInsets = mTmpRect;
             }
-            mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds);
+            mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
+                    minimizedHomeBounds);
             if (DEBUG_RECENTS_ANIMATIONS) {
                 Slog.d(TAG, "startAnimation(): Notify animation start:");
                 for (int i = 0; i < mPendingAnimations.size(); i++) {
@@ -495,6 +498,32 @@
         mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
     }
 
+    private RemoteAnimationTarget[] createAppAnimations() {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
+            final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
+            if (target != null) {
+                targets.add(target);
+            } else {
+                removeAnimation(taskAdapter);
+            }
+        }
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the recents
+                        // animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
         cancelAnimation(reorderMode, false /*screenshot */, reason);
     }
@@ -592,7 +621,7 @@
             return null;
         }
 
-        final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(task,
+        final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(mService.mSurfaceControlFactory, task,
                 new SurfaceControl.ScreenshotGraphicBuffer(taskSnapshot.getSnapshot(),
                         taskSnapshot.getColorSpace(), false /* containsSecureLayers */));
         mRecentScreenshotAnimator = new SurfaceAnimator(
@@ -619,6 +648,11 @@
             removeAnimation(taskAdapter);
         }
 
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
+            removeWallpaperAnimation(wallpaperAdapter);
+        }
+
         // Clear any pending failsafe runnables
         mService.mH.removeCallbacks(mFailsafeRunnable);
         mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
@@ -747,6 +781,15 @@
         return false;
     }
 
+    boolean isAnimatingWallpaper(WallpaperWindowToken token) {
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            if (token == mPendingWallpaperAnimations.get(i).getToken()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean isAnimatingApp(AppWindowToken appToken) {
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final Task task = mPendingAnimations.get(i).mTask;
@@ -760,6 +803,11 @@
         return false;
     }
 
+    boolean shouldIgnoreForAccessibility(WindowState windowState) {
+        final Task task = windowState.getTask();
+        return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mAppToken);
+    }
+
     @VisibleForTesting
     class TaskAnimationAdapter implements AnimationAdapter {
 
@@ -779,7 +827,7 @@
             mBounds.set(container.getDisplayedBounds());
         }
 
-        RemoteAnimationTarget createRemoteAnimationApp() {
+        RemoteAnimationTarget createRemoteAnimationTarget() {
             final AppWindowToken topApp = mTask.getTopVisibleAppToken();
             final WindowState mainWindow = topApp != null
                     ? topApp.findMainWindow()
@@ -806,11 +854,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             // Restore z-layering, position and stack crop until client has a chance to modify it.
@@ -825,6 +868,7 @@
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
+            // Cancel the animation immediately if any single task animator is canceled
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
         }
 
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 7448e00..87bda4a 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -58,6 +58,8 @@
     private final WindowManagerService mService;
     private final RemoteAnimationAdapter mRemoteAnimationAdapter;
     private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final Rect mTmpRect = new Rect();
     private final Handler mHandler;
     private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -110,16 +112,21 @@
                 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
         mFinishedCallback = new FinishedCallback(this);
 
-        final RemoteAnimationTarget[] animations = createAnimations();
-        if (animations.length == 0) {
+        // Create the app targets
+        final RemoteAnimationTarget[] appTargets = createAppAnimations();
+        if (appTargets.length == 0) {
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate");
             onAnimationFinished();
             return;
         }
+
+        // Create the remote wallpaper animation targets (if any)
+        final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
                 linkToDeathOfRunner();
-                mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback);
+                mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets,
+                        mFinishedCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to start remote animation", e);
                 onAnimationFinished();
@@ -155,8 +162,8 @@
         Slog.i(TAG, sw.toString());
     }
 
-    private RemoteAnimationTarget[] createAnimations() {
-        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
+    private RemoteAnimationTarget[] createAppAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAppAnimations()");
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
@@ -186,6 +193,19 @@
         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
     }
 
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+                mRemoteAnimationAdapter.getDuration(),
+                mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the app animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     private void onAnimationFinished() {
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations="
                 + mPendingAnimations.size());
@@ -207,7 +227,15 @@
                         adapters.mThumbnailAdapter.mCapturedFinishCallback
                                 .onAnimationFinished(adapters.mThumbnailAdapter);
                     }
-                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
+                    mPendingAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tapp=" + adapters.mAppWindowToken);
+                }
+
+                for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+                    final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
+                    adapter.getLeashFinishedCallback().onAnimationFinished(adapter);
+                    mPendingWallpaperAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\twallpaper=" + adapter.getToken());
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Failed to finish remote animation", e);
@@ -390,11 +418,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
@@ -424,10 +447,7 @@
                 mPendingAnimations.remove(mRecord);
             }
             if (mPendingAnimations.isEmpty()) {
-                mHandler.removeCallbacks(mTimeoutRunnable);
-                releaseFinishedCallback();
-                invokeAnimationCancelled();
-                setRunningRemoteAnimation(false);
+                cancelAnimation("allAppAnimationsCanceled");
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index eb5d096..734f224 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -27,12 +27,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
@@ -93,7 +91,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
@@ -196,6 +193,9 @@
     /** Set when a power hint has started, but not ended. */
     private boolean mPowerHintSent;
 
+    /** Used to keep ensureActivitiesVisible() from being entered recursively. */
+    private boolean mInEnsureActivitiesVisible = false;
+
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
     int mDefaultMinSizeOfResizeableTaskDp = -1;
@@ -808,8 +808,14 @@
      */
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
-        mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
+        if (mInEnsureActivitiesVisible) {
+            // Don't do recursive work.
+            return;
+        }
+        mInEnsureActivitiesVisible = true;
+
         try {
+            mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -818,6 +824,7 @@
             }
         } finally {
             mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
+            mInEnsureActivitiesVisible = false;
         }
     }
 
@@ -881,48 +888,6 @@
         }
     }
 
-    void resizeStack(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,
-            boolean deferResume) {
-
-        if (stack.inSplitScreenPrimaryWindowingMode()) {
-            mStackSupervisor.resizeDockedStackLocked(bounds, tempTaskBounds,
-                    tempTaskInsetBounds, null, null, preserveWindows, deferResume);
-            return;
-        }
-
-        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();
-        if (!allowResizeInDockedMode
-                && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
-            // If the docked stack exists, don't resize non-floating stacks independently of the
-            // size computed from the docked stack size (otherwise they will be out of sync)
-            return;
-        }
-
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
-        mWindowManager.deferSurfaceLayout();
-        try {
-            if (stack.affectedBySplitScreenResize()) {
-                if (bounds == null && stack.inSplitScreenWindowingMode()) {
-                    // null bounds = fullscreen windowing mode...at least for now.
-                    stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-                } else if (splitScreenActive) {
-                    // If we are in split-screen mode and this stack support split-screen, then
-                    // it should be split-screen secondary mode. i.e. adjacent to the docked stack.
-                    stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-                }
-            }
-            stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);
-            if (!deferResume) {
-                stack.ensureVisibleActivitiesConfigurationLocked(
-                        stack.topRunningActivityLocked(), preserveWindows);
-            }
-        } finally {
-            mWindowManager.continueSurfaceLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
     /**
      * Move stack with all its existing content to specified display.
      * @param stackId Id of stack to move.
@@ -990,8 +955,7 @@
 
     void moveActivityToPinnedStack(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
             String reason) {
-
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         final ActivityDisplay display = r.getActivityStack().getDisplay();
         ActivityStack stack = display.getPinnedStack();
@@ -1005,18 +969,13 @@
         // Need to make sure the pinned stack exist so we can resize it below...
         stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
-        // Calculate the target bounds here before the task is reparented back into pinned windowing
-        // mode (which will reset the saved bounds)
-        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
-
         try {
             final TaskRecord task = r.getTaskRecord();
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
-            resizeStack(stack, task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
-                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
-                    true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            stack.resize(task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
+                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
 
             if (task.mActivities.size() == 1) {
                 // Defer resume until below, and do not schedule PiP changes until we animate below
@@ -1045,12 +1004,17 @@
             // to the pinned stack
             r.supportsEnterPipOnTaskSwitch = false;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
-        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
-                true /* fromFullscreen */);
+        // Notify the pinned stack controller to prepare the PiP animation, expect callback
+        // delivered from SystemUI to WM to start the animation.
+        final PinnedStackController pinnedStackController =
+                display.mDisplayContent.getPinnedStackController();
+        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                null /* stackBounds */);
 
+        // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
         // stack.  This MUST run after the animation above is scheduled to ensure that the windows
         // drawn signal is scheduled after the bounds animation start call on the bounds animator
@@ -1661,7 +1625,8 @@
 
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
-        return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */);
+        return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */,
+                -1 /* no realCallingPid */, -1 /* no realCallingUid */);
     }
 
     /**
@@ -1670,13 +1635,16 @@
      * @param r The activity we are trying to launch. Can be null.
      * @param options The activity options used to the launch. Can be null.
      * @param candidateTask The possible task the activity might be launched in. Can be null.
-     * @params launchParams The resolved launch params to use.
+     * @param launchParams The resolved launch params to use.
+     * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
+     * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
      *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
-            @Nullable LaunchParamsController.LaunchParams launchParams) {
+            @Nullable LaunchParamsController.LaunchParams launchParams, int realCallingPid,
+            int realCallingUid) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
         //Rect bounds = null;
@@ -1707,7 +1675,15 @@
         if (launchParams != null && launchParams.mPreferredDisplayId != INVALID_DISPLAY) {
             displayId = launchParams.mPreferredDisplayId;
         }
-        if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
+        final boolean canLaunchOnDisplayFromStartRequest =
+                realCallingPid != 0 && realCallingUid > 0 && r != null
+                        && mStackSupervisor.canPlaceEntityOnDisplay(displayId, realCallingPid,
+                        realCallingUid, r.info);
+        // Checking if the activity's launch caller, or the realCallerId of the activity from
+        // start request (i.e. entity that invokes PendingIntent) is allowed to launch on the
+        // display.
+        if (displayId != INVALID_DISPLAY && (canLaunchOnDisplay(r, displayId)
+                || canLaunchOnDisplayFromStartRequest)) {
             if (r != null) {
                 stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
                         launchParams);
@@ -2106,7 +2082,7 @@
      * @param userId user handle for the locked managed profile.
      */
     void lockAllProfileTasks(@UserIdInt int userId) {
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -2127,7 +2103,7 @@
                 }
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -2260,9 +2236,9 @@
     void getRunningTasks(int maxNum, List<ActivityManager.RunningTaskInfo> list,
             @WindowConfiguration.ActivityType int ignoreActivityType,
             @WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid,
-            boolean allowed) {
+            boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
         mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType,
-                ignoreWindowingMode, mActivityDisplays, callingUid, allowed);
+                ignoreWindowingMode, mActivityDisplays, callingUid, allowed, crossUser, profileIds);
     }
 
     void sendPowerHintForLaunchStartIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 968d02b..4365d03 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -134,7 +134,7 @@
 
     // Only a separate transaction until we separate the apply surface changes
     // transaction from the global transaction.
-    private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mDisplayTransaction;
 
     private final Consumer<WindowState> mCloseSystemDialogsConsumer = w -> {
         if (w.mHasSurface) {
@@ -154,6 +154,7 @@
 
     RootWindowContainer(WindowManagerService service) {
         super(service);
+        mDisplayTransaction = service.mTransactionFactory.get();
         mHandler = new MyHandler(service.mH.getLooper());
     }
 
@@ -471,8 +472,7 @@
 
         final int count = mChildren.size();
         for (int i = 0; i < count; ++i) {
-            final DisplayContent dc = mChildren.get(i);
-            final int pendingChanges = animator.getPendingLayoutChanges(dc.getDisplayId());
+            final int pendingChanges = mChildren.get(i).pendingLayoutChanges;
             if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
                 animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
             }
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 3bf437d..81a8547 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.util.ArraySet;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -40,7 +41,7 @@
 
     void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
             @WindowingMode int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
-            int callingUid, boolean allowed) {
+            int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
         // Return early if there are no tasks to fetch
         if (maxNum <= 0) {
             return;
@@ -55,7 +56,7 @@
                 final ActivityStack stack = display.getChildAt(stackNdx);
                 mTmpStackTasks.clear();
                 stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
-                        callingUid, allowed);
+                        callingUid, allowed, crossUser, profileIds);
                 mTmpSortedSet.addAll(mTmpStackTasks);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 82dde0d..cbaf098 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -127,7 +127,7 @@
         mOriginalWidth = originalWidth;
         mOriginalHeight = originalHeight;
 
-        final SurfaceControl.Transaction t = mService.mTransactionFactory.make();
+        final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
         try {
             mSurfaceControl = displayContent.makeOverlay()
                     .setName("ScreenshotSurface")
@@ -137,13 +137,13 @@
 
             // In case display bounds change, screenshot buffer and surface may mismatch so set a
             // scaling mode.
-            SurfaceControl.Transaction t2 = mService.mTransactionFactory.make();
+            SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
             t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW);
             t2.apply(true /* sync */);
 
             // Capture a screenshot into the surface we just created.
             final int displayId = display.getDisplayId();
-            final Surface surface = mService.mSurfaceFactory.make();
+            final Surface surface = mService.mSurfaceFactory.get();
             surface.copyFrom(mSurfaceControl);
             SurfaceControl.ScreenshotGraphicBuffer gb =
                     mService.mDisplayManagerInternal.screenshot(displayId);
@@ -427,7 +427,7 @@
                 Slog.i(TAG_WM,
                         "  FREEZE " + mSurfaceControl + ": DESTROY");
             }
-            mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
+            mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
             mSurfaceControl = null;
         }
         if (mExitingBlackFrame != null) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 4a76042..72bb355 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -227,8 +228,7 @@
     @Override
     public void finishDrawing(IWindow window,
             @Nullable SurfaceControl.Transaction postDrawTransaction) {
-        if (WindowManagerService.localLOGV) Slog.v(
-            TAG_WM, "IWindow finishDrawing called for " + window);
+        if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
         mService.finishDrawingWindow(this, window, postDrawTransaction);
     }
 
@@ -474,8 +474,9 @@
         mPackageName = packageName;
         mRelayoutTag = "relayoutWindow: " + mPackageName;
         if (mSurfaceSession == null) {
-            if (WindowManagerService.localLOGV) Slog.v(
-                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+            if (DEBUG) {
+                Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+            }
             mSurfaceSession = new SurfaceSession();
             if (SHOW_TRANSACTIONS) Slog.i(
                     TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
@@ -565,8 +566,10 @@
             return;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG_WM, "Last window removed from " + this
-                + ", destroying " + mSurfaceSession);
+        if (DEBUG) {
+            Slog.v(TAG_WM, "Last window removed from " + this
+                    + ", destroying " + mSurfaceSession);
+        }
         if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  KILL SURFACE SESSION " + mSurfaceSession);
         try {
             mSurfaceSession.kill();
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index 82f2ad8..9e5d9ca 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -27,17 +27,20 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class StrictModeFlash {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "StrictModeFlash" : TAG_WM;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
     private final int mThickness = 20;
 
-    public StrictModeFlash(DisplayContent dc) {
+    StrictModeFlash(Supplier<Surface> surfaceFactory, DisplayContent dc) {
+        mSurface = surfaceFactory.get();
         SurfaceControl ctrl = null;
         try {
             ctrl = dc.makeOverlay()
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 85176be..bbd986f 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -39,6 +39,8 @@
 import com.android.server.AnimationThread;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
+import java.util.function.Supplier;
+
 /**
  * Class to run animations without holding the window manager lock.
  */
@@ -73,9 +75,10 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
-    SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
-        this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
-                powerManagerInternal);
+    SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
+            PowerManagerInternal powerManagerInternal) {
+        this(null /* callbackProvider */, null /* animatorFactory */,
+                transactionFactory.get(), powerManagerInternal);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index cd211a2..ba728ba 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -54,6 +54,7 @@
     final Animatable mAnimatable;
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     @VisibleForTesting
+    @Nullable
     final Runnable mAnimationFinishedCallback;
     private boolean mAnimationStartDelayed;
 
@@ -262,7 +263,7 @@
             if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
-            if (!restarting) {
+            if (!restarting && mAnimationFinishedCallback != null) {
                 mAnimationFinishedCallback.run();
             }
         }
diff --git a/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
deleted file mode 100644
index 5390e5a..0000000
--- a/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.SurfaceSession;
-import android.view.SurfaceControl;
-
-interface SurfaceBuilderFactory {
-    SurfaceControl.Builder make(SurfaceSession s);
-};
-
diff --git a/services/core/java/com/android/server/wm/SurfaceFactory.java b/services/core/java/com/android/server/wm/SurfaceFactory.java
deleted file mode 100644
index 076b7df..0000000
--- a/services/core/java/com/android/server/wm/SurfaceFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.Surface;
-
-/**
- * Helper class to inject custom {@link Surface} objects into window manager.
- */
-interface SurfaceFactory {
-    Surface make();
-};
-
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3a2eb57..5e14087 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -85,8 +85,6 @@
     private Rect mTmpRect = new Rect();
     // For handling display rotations.
     private Rect mTmpRect2 = new Rect();
-    // For retrieving dim bounds
-    private Rect mTmpRect3 = new Rect();
 
     // Resize mode of the task. See {@link ActivityInfo#resizeMode}
     private int mResizeMode;
@@ -613,6 +611,9 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
+        if (WindowManagerService.sHierarchicalAnimations) {
+            return super.getAnimationLeashParent();
+        }
         // Currently, only the recents animation will create animation leashes for tasks. In this
         // case, reparent the task to the home animation layer while it is being animated to allow
         // the home activity to reorder the app windows relative to its own.
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index c2c4767..5e8831d 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -57,6 +57,7 @@
     private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
     private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 23;
     private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24;
+    private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -161,6 +162,10 @@
         l.onSingleTaskDisplayDrawn(m.arg1);
     };
 
+    private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+        l.onSingleTaskDisplayEmpty(m.arg1);
+    };
+
     private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
         l.onTaskDisplayChanged(m.arg1, m.arg2);
     };
@@ -251,6 +256,9 @@
                 case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
                     forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
                     break;
+                case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+                    forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+                    break;
                 case NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyTaskDisplayChanged, msg);
                     break;
@@ -513,6 +521,17 @@
     }
 
     /**
+     * Notify listeners that the last task is removed from a single task display.
+     */
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        final Message msg = mHandler.obtainMessage(
+                NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+                displayId, 0 /* unused */);
+        forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+        msg.sendToTarget();
+    }
+
+    /**
      * Notify listeners that a task is reparented to another display.
      */
     void notifyTaskDisplayChanged(int taskId, int newDisplayId) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 8b0b6ce..42866f9 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -64,10 +64,6 @@
 
     private static Factory sFactory;
 
-    // The margin the pointer position has to be within the side of the screen to be
-    // considered at the side of the screen.
-    static final int SIDE_MARGIN_DIP = 100;
-
     @IntDef(flag = true,
             value = {
                     CTRL_NONE,
@@ -101,7 +97,6 @@
     private DisplayContent mDisplayContent;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private Rect mTmpRect = new Rect();
-    private int mSideMargin;
     private int mMinVisibleWidth;
     private int mMinVisibleHeight;
 
@@ -309,7 +304,6 @@
         // Notify InputMonitor to take mDragWindowHandle.
         mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
 
-        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
         display.getRealSize(mMaxVisibleSize);
@@ -488,12 +482,6 @@
         int right = mWindowOriginalBounds.right;
         int bottom = mWindowOriginalBounds.bottom;
 
-        // The aspect which we have to respect. Note that if the orientation does not need to be
-        // preserved the aspect will be calculated as 1.0 which neutralizes the following
-        // computations.
-        final float minAspect = !mPreserveOrientation
-                ? 1.0f
-                : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
         // Calculate the resulting width and height of the drag operation.
         int width = right - left;
         int height = bottom - top;
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index ede2f56..0ea108e 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -71,7 +71,6 @@
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
@@ -513,7 +512,7 @@
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
             // default configuration the next time it launches.
-            updateOverrideConfiguration(null);
+            setBounds(null);
         }
         mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId);
     }
@@ -546,7 +545,7 @@
     }
 
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (!isResizeable()) {
@@ -566,7 +565,7 @@
                 // Task doesn't exist in window manager yet (e.g. was restored from recents).
                 // All we can do for now is update the bounds so it can be used when the task is
                 // added to window manager.
-                updateOverrideConfiguration(bounds);
+                setBounds(bounds);
                 if (!inFreeformWindowingMode()) {
                     // re-restore the task so it can have the proper stack association.
                     mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
@@ -585,7 +584,11 @@
 
             Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
 
-            final boolean updatedConfig = updateOverrideConfiguration(bounds);
+            boolean updatedConfig = false;
+            mTmpConfig.setTo(getResolvedOverrideConfiguration());
+            if (setBounds(bounds) != BOUNDS_CHANGE_NONE) {
+                updatedConfig = !mTmpConfig.equals(getResolvedOverrideConfiguration());
+            }
             // This variable holds information whether the configuration didn't change in a significant
 
             // way and the activity was kept the way it was. If it's false, it means the activity
@@ -616,7 +619,7 @@
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             return kept;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -702,7 +705,7 @@
                 && toStack.topRunningActivityLocked() != null) {
             // Pause the resumed activity on the target stack while re-parenting task on top of it.
             toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
-                    null /* resuming */, false /* pauseImmediately */);
+                    null /* resuming */);
         }
 
         final int toStackWindowingMode = toStack.getWindowingMode();
@@ -722,7 +725,7 @@
             windowManager.setWillReplaceWindow(topActivity.appToken, animate);
         }
 
-        windowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
@@ -806,7 +809,7 @@
                         !mightReplaceWindow, deferResume);
             }
         } finally {
-            windowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (mightReplaceWindow) {
@@ -1082,8 +1085,8 @@
 
         clearRootProcess();
 
-        // TODO: Use window container controller once tasks are better synced between AM and WM
-        mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
+        mService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+                taskId, userId);
     }
 
     void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
@@ -1405,8 +1408,7 @@
      * Completely remove all activities associated with an existing
      * task starting at a specified index.
      */
-    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
-            String reason) {
+    final void performClearTaskAtIndexLocked(int activityNdx, String reason) {
         int numActivities = mActivities.size();
         for ( ; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
@@ -1419,8 +1421,8 @@
                 mActivities.remove(activityNdx);
                 --activityNdx;
                 --numActivities;
-            } else if (r.finishIfPossible(Activity.RESULT_CANCELED,
-                    null /* resultData */, reason, false /* oomAdj */, pauseImmediately)
+            } else if (r.finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
+                    false /* oomAdj */)
                     == FINISH_RESULT_REMOVED) {
                 --activityNdx;
                 --numActivities;
@@ -1433,7 +1435,7 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
+        performClearTaskAtIndexLocked(0, "clear-task-all");
         mReuseTask = false;
     }
 
@@ -1501,9 +1503,9 @@
         return null;
     }
 
-    void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
+    void removeTaskActivitiesLocked(String reason) {
         // Just remove the entire task.
-        performClearTaskAtIndexLocked(0, pauseImmediately, reason);
+        performClearTaskAtIndexLocked(0, reason);
     }
 
     String lockTaskAuthToString() {
@@ -1805,15 +1807,6 @@
         }
     }
 
-    /**
-     * Update task's override configuration based on the bounds.
-     * @param bounds The bounds of the task.
-     * @return True if the override configuration was updated.
-     */
-    boolean updateOverrideConfiguration(Rect bounds) {
-        return updateOverrideConfiguration(bounds, null /* insetBounds */);
-    }
-
     void setLastNonFullscreenBounds(Rect bounds) {
         if (mLastNonFullscreenBounds == null) {
             mLastNonFullscreenBounds = new Rect(bounds);
@@ -1823,32 +1816,6 @@
     }
 
     /**
-     * Update task's override configuration based on the bounds.
-     * @param bounds The bounds of the task.
-     * @param insetBounds The bounds used to calculate the system insets, which is used here to
-     *                    subtract the navigation bar/status bar size from the screen size reported
-     *                    to the application. See {@link IActivityTaskManager#resizeDockedStack}.
-     * @return True if the override configuration was updated.
-     */
-    boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
-        final boolean hasSetDisplayedBounds = (insetBounds != null && !insetBounds.isEmpty());
-        if (hasSetDisplayedBounds) {
-            setDisplayedBounds(bounds);
-        } else {
-            setDisplayedBounds(null);
-        }
-        // "steady" bounds do not include any temporary offsets from animation or interaction.
-        Rect steadyBounds = hasSetDisplayedBounds ? insetBounds : bounds;
-        if (equivalentRequestedOverrideBounds(steadyBounds)) {
-            return false;
-        }
-
-        mTmpConfig.setTo(getResolvedOverrideConfiguration());
-        setBounds(steadyBounds);
-        return !mTmpConfig.equals(getResolvedOverrideConfiguration());
-    }
-
-    /**
      * This should be called when an child activity changes state. This should only
      * be called from
      * {@link ActivityRecord#setState(ActivityState, String)} .
@@ -2299,7 +2266,7 @@
 
     Rect updateOverrideConfigurationFromLaunchBounds() {
         final Rect bounds = getLaunchBounds();
-        updateOverrideConfiguration(bounds);
+        setBounds(bounds);
         if (bounds != null && !bounds.isEmpty()) {
             // TODO: Review if we actually want to do this - we are setting the launch bounds
             // directly here.
@@ -2324,12 +2291,12 @@
                 return;
             }
             if (mLastNonFullscreenBounds != null) {
-                updateOverrideConfiguration(mLastNonFullscreenBounds);
+                setBounds(mLastNonFullscreenBounds);
             } else {
                 mService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
             }
         } else {
-            updateOverrideConfiguration(inStack.getRequestedOverrideBounds());
+            setBounds(inStack.getRequestedOverrideBounds());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index d6c2f66..d36ebf0 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -23,6 +23,8 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
+import java.util.function.Function;
+
 /**
  * Class used by {@link RecentsAnimationController} to create a surface control with taking
  * screenshot of task when canceling recents animation.
@@ -36,7 +38,7 @@
     private int mWidth;
     private int mHeight;
 
-    TaskScreenshotAnimatable(Task task,
+    TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory, Task task,
             SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
         GraphicBuffer buffer = screenshotBuffer == null
                 ? null : screenshotBuffer.getGraphicBuffer();
@@ -47,7 +49,7 @@
             Slog.d(TAG, "Creating TaskScreenshotAnimatable: task: " + task
                     + "width: " + mWidth + "height: " + mHeight);
         }
-        mSurfaceControl = new SurfaceControl.Builder(new SurfaceSession())
+        mSurfaceControl = surfaceControlFactory.apply(new SurfaceSession())
                 .setName("RecentTaskScreenshotSurface")
                 .setBufferSize(mWidth, mHeight)
                 .build();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 32a1d90..a156f5c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -29,11 +29,11 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.AtomicFile;
 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 1b3e4c1..7456f0d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -237,7 +237,7 @@
             int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
             int currentOrientation) {
         mService = service;
-        mSurface = new Surface();
+        mSurface = service.mSurfaceFactory.get();
         mHandler = new Handler(mService.mH.getLooper());
         mSession = WindowManagerGlobal.getWindowSession();
         mWindow = window;
@@ -325,13 +325,13 @@
                 - ((float) mFrame.width() / mFrame.height())) > 0.01f;
 
         // Keep a reference to it such that it doesn't get destroyed when finalized.
-        mChildSurfaceControl = new SurfaceControl.Builder(session)
+        mChildSurfaceControl = mService.mSurfaceControlFactory.apply(session)
                 .setName(mTitle + " - task-snapshot-surface")
                 .setBufferSize(buffer.getWidth(), buffer.getHeight())
                 .setFormat(buffer.getFormat())
                 .setParent(mSurfaceControl)
                 .build();
-        Surface surface = new Surface();
+        Surface surface = mService.mSurfaceFactory.get();
         surface.copyFrom(mChildSurfaceControl);
 
         final Rect frame;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index cc2112e..10d8328 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -45,7 +45,6 @@
 import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT;
 import static com.android.server.wm.StackProto.ADJUST_IME_AMOUNT;
 import static com.android.server.wm.StackProto.ANIMATING_BOUNDS;
-import static com.android.server.wm.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
 import static com.android.server.wm.StackProto.BOUNDS;
 import static com.android.server.wm.StackProto.DEFER_REMOVAL;
 import static com.android.server.wm.StackProto.FILLS_PARENT;
@@ -111,12 +110,6 @@
      */
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
-    private SurfaceControl mAnimationBackgroundSurface;
-    private boolean mAnimationBackgroundSurfaceIsShown = false;
-
-    /** The particular window with an Animation with non-zero background color. */
-    private WindowStateAnimator mAnimationBackgroundAnimator;
-
     /** Application tokens that are exiting, but still on screen for animations. */
     final AppTokenList mExitingAppTokens = new AppTokenList();
     final AppTokenList mTmpAppTokens = new AppTokenList();
@@ -230,39 +223,6 @@
         }
     }
 
-    private void updateAnimationBackgroundBounds() {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getRawBounds(mTmpRect);
-        final Rect stackBounds = getBounds();
-        getPendingTransaction()
-                .setWindowCrop(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
-                .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
-                        mTmpRect.top - stackBounds.top);
-        scheduleAnimation();
-    }
-
-    private void hideAnimationSurface() {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getPendingTransaction().hide(mAnimationBackgroundSurface);
-        mAnimationBackgroundSurfaceIsShown = false;
-        scheduleAnimation();
-    }
-
-    private void showAnimationSurface(float alpha) {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
-                .setAlpha(mAnimationBackgroundSurface, alpha)
-                .show(mAnimationBackgroundSurface);
-        mAnimationBackgroundSurfaceIsShown = true;
-        scheduleAnimation();
-    }
-
     @Override
     public int setBounds(Rect bounds) {
         return setBounds(getRequestedOverrideBounds(), bounds);
@@ -275,10 +235,6 @@
 
         final int result = super.setBounds(bounds);
 
-        if (getParent() != null) {
-            updateAnimationBackgroundBounds();
-        }
-
         updateAdjustedBounds();
 
         updateSurfaceBounds();
@@ -738,7 +694,6 @@
         // surface position.
         updateSurfaceSize(getPendingTransaction());
         final int windowingMode = getWindowingMode();
-        final boolean isAlwaysOnTop = isAlwaysOnTop();
 
         if (mDisplayContent == null) {
             return;
@@ -822,11 +777,6 @@
         super.onDisplayChanged(dc);
 
         updateSurfaceBounds();
-        if (mAnimationBackgroundSurface == null) {
-            mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer()
-                    .setName("animation background stackId=" + mStackId)
-                    .build();
-        }
     }
 
     /**
@@ -859,7 +809,7 @@
         // When the home stack is resizable, should always have the same stack and task bounds
         if (isActivityTypeHome()) {
             final Task homeTask = findHomeTask();
-            if (homeTask != null && homeTask.isResizeable()) {
+            if (homeTask == null || homeTask.isResizeable()) {
                 // Calculate the home stack bounds when in docked mode and the home stack is
                 // resizeable.
                 getDisplayContent().mDividerControllerLocked
@@ -965,7 +915,7 @@
     }
 
     void resetDockedStackToMiddle() {
-        if (inSplitScreenPrimaryWindowingMode()) {
+        if (!inSplitScreenPrimaryWindowingMode()) {
             throw new IllegalStateException("Not a docked stack=" + this);
         }
 
@@ -973,12 +923,12 @@
 
         final Rect bounds = new Rect();
         final Rect tempBounds = new Rect();
-        TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
-        Rect dockedBounds =
-                (dockedStack == null || dockedStack == this) ? null : dockedStack.getRawBounds();
-        getStackDockedModeBoundsLocked(mDisplayContent.getConfiguration(), dockedBounds,
+        getStackDockedModeBoundsLocked(mDisplayContent.getConfiguration(), null /* dockedBounds */,
                 null /* currentTempTaskBounds */, bounds, tempBounds);
-        mActivityStack.requestResize(bounds);
+        mActivityStack.mStackSupervisor.resizeDockedStackLocked(bounds, null /* tempTaskBounds */,
+                null /* tempTaskInsetBounds */, null /* tempOtherTaskBounds */,
+                null /* tempOtherTaskInsetBounds */, false /* preserveWindows */,
+                false /* deferResume */);
     }
 
     @Override
@@ -1008,27 +958,10 @@
 
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
-        if (mAnimationBackgroundSurface != null) {
-            mWmService.mTransactionFactory.make().remove(mAnimationBackgroundSurface).apply();
-            mAnimationBackgroundSurface = null;
-        }
-
         mDisplayContent = null;
         mWmService.mWindowPlacerLocked.requestTraversal();
     }
 
-    void resetAnimationBackgroundAnimator() {
-        mAnimationBackgroundAnimator = null;
-        hideAnimationSurface();
-    }
-
-    void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
-        if (mAnimationBackgroundAnimator == null) {
-            mAnimationBackgroundAnimator = winAnimator;
-            showAnimationSurface(((color >> 24) & 0xff) / 255f);
-        }
-    }
-
     // TODO: Should each user have there own stacks?
     @Override
     void switchUser() {
@@ -1365,7 +1298,6 @@
         }
         proto.write(FILLS_PARENT, matchParentBounds());
         getRawBounds().writeToProto(proto, BOUNDS);
-        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
         proto.write(DEFER_REMOVAL, mDeferRemoval);
         proto.write(MINIMIZE_AMOUNT, mMinimizeAmount);
         proto.write(ADJUSTED_FOR_IME, mAdjustedForIme);
@@ -1395,9 +1327,6 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
         }
-        if (mAnimationBackgroundSurfaceIsShown) {
-            pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
-        }
         if (!mExitingAppTokens.isEmpty()) {
             pw.println();
             pw.println("  Exiting application tokens:");
@@ -1661,40 +1590,6 @@
     }
 
     /**
-     * @return the current stack bounds transformed to the given {@param aspectRatio}. If
-     *         the default bounds is {@code null}, then the {@param aspectRatio} is applied to the
-     *         default bounds.
-     */
-    Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
-        if (!mWmService.mAtmService.mSupportsPictureInPicture) {
-            return null;
-        }
-
-        final DisplayContent displayContent = getDisplayContent();
-        if (displayContent == null) {
-            return null;
-        }
-
-        if (!inPinnedWindowingMode()) {
-            return null;
-        }
-
-        final PinnedStackController pinnedStackController =
-                displayContent.getPinnedStackController();
-        if (stackBounds == null) {
-            // Calculate the aspect ratio bounds from the default bounds
-            stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
-        }
-
-        if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
-            return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
-        } else {
-            return stackBounds;
-        }
-    }
-
-    /**
      * Animates the pinned stack.
      */
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
@@ -1746,6 +1641,11 @@
             if (toBounds.width() == fromBounds.width()
                     && toBounds.height() == fromBounds.height()) {
                 intendedAnimationType = BoundsAnimationController.BOUNDS;
+            } else if (!fromFullscreen && !toBounds.equals(fromBounds)) {
+                // intendedAnimationType may have been reset at the end of RecentsAnimation,
+                // force it to BOUNDS type if we know for certain we're animating to
+                // a different bounds, especially for expand and collapse of PiP window.
+                intendedAnimationType = BoundsAnimationController.BOUNDS;
             }
         }
 
@@ -1766,6 +1666,11 @@
             return;
         }
 
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
         if (!inPinnedWindowingMode()) {
             return;
         }
@@ -1776,13 +1681,10 @@
         if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) {
             return;
         }
-        getAnimationOrCurrentBounds(mTmpFromBounds);
-        mTmpToBounds.set(mTmpFromBounds);
-        getPictureInPictureBounds(aspectRatio, mTmpToBounds);
-        if (!mTmpToBounds.equals(mTmpFromBounds)) {
-            animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
-                    -1 /* duration */, false /* fromFullscreen */);
-        }
+
+        // Notify the pinned stack controller about aspect ratio change.
+        // This would result a callback delivered from SystemUI to WM to start animation,
+        // if the bounds are ought to be altered due to aspect ratio change.
         pinnedStackController.setAspectRatio(
                 pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
                         ? aspectRatio : -1f);
diff --git a/services/core/java/com/android/server/wm/TransactionFactory.java b/services/core/java/com/android/server/wm/TransactionFactory.java
deleted file mode 100644
index 067f083..0000000
--- a/services/core/java/com/android/server/wm/TransactionFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.view.SurfaceControl.Transaction;
-
-/**
- * Helper class to inject custom transaction objects into window manager.
- */
-interface TransactionFactory {
-    Transaction make();
-};
-
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
new file mode 100644
index 0000000..895350b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * An animation adapter for wallpaper windows.
+ */
+class WallpaperAnimationAdapter implements AnimationAdapter {
+    private static final String TAG = "WallpaperAnimationAdapter";
+
+    private final WallpaperWindowToken mWallpaperToken;
+    private SurfaceControl mCapturedLeash;
+    private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
+
+    private long mDurationHint;
+    private long mStatusBarTransitionDelay;
+
+    private Consumer<WallpaperAnimationAdapter> mAnimationCanceledRunnable;
+    private RemoteAnimationTarget mTarget;
+
+    WallpaperAnimationAdapter(WallpaperWindowToken wallpaperToken,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable) {
+        mWallpaperToken = wallpaperToken;
+        mDurationHint = durationHint;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+        mAnimationCanceledRunnable = animationCanceledRunnable;
+    }
+
+    /**
+     * Creates and starts remote animations for all the visible wallpaper windows.
+     *
+     * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
+     */
+    public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
+            ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
+            if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
+                if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                    Slog.d(TAG, "\tNot visible=" + wallpaperWindow);
+                }
+                return;
+            }
+
+            if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                Slog.d(TAG, "\tvisible=" + wallpaperWindow);
+            }
+            final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
+                    wallpaperWindow, durationHint, statusBarTransitionDelay,
+                    animationCanceledRunnable);
+            wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(),
+                    wallpaperAdapter, false /* hidden */);
+            targets.add(wallpaperAdapter.createRemoteAnimationTarget());
+            adaptersOut.add(wallpaperAdapter);
+        });
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    /**
+     * Create a remote animation target for this animation adapter.
+     */
+    RemoteAnimationTarget createRemoteAnimationTarget() {
+        mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
+                mWallpaperToken.getPrefixOrderIndex(), new Point(), null,
+                mWallpaperToken.getWindowConfiguration(), true, null, null);
+        return mTarget;
+    }
+
+    /**
+     * @return the leash for this animation (only valid after the wallpaper window surface animation
+     * has started).
+     */
+    SurfaceControl getLeash() {
+        return mCapturedLeash;
+    }
+
+    /**
+     * @return the callback to call to clean up when the animation has finished.
+     */
+    SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
+        return mCapturedLeashFinishCallback;
+    }
+
+    /**
+     * @return the wallpaper window
+     */
+    WallpaperWindowToken getToken() {
+        return mWallpaperToken;
+    }
+
+    @Override
+    public boolean getShowWallpaper() {
+        // Not used
+        return false;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
+
+        // Restore z-layering until client has a chance to modify it.
+        t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex());
+        mCapturedLeash = animationLeash;
+        mCapturedLeashFinishCallback = finishCallback;
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationCancelled");
+        mAnimationCanceledRunnable.accept(this);
+    }
+
+    @Override
+    public long getDurationHint() {
+        return mDurationHint;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("token=");
+        pw.println(mWallpaperToken);
+        if (mTarget != null) {
+            pw.print(prefix);
+            pw.println("Target:");
+            mTarget.dump(pw, prefix + "  ");
+        } else {
+            pw.print(prefix);
+            pw.println("Target: null");
+        }
+    }
+
+    @Override
+    public void writeToProto(ProtoOutputStream proto) {
+        final long token = proto.start(REMOTE);
+        if (mTarget != null) {
+            mTarget.writeToProto(proto, TARGET);
+        }
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6147272..13902ee 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -110,7 +110,6 @@
     private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
 
     private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
-        final WindowAnimator winAnimator = mService.mAnimator;
         if ((w.mAttrs.type == TYPE_WALLPAPER)) {
             if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
                 mFindResults.setTopWallpaper(w);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e15b783..528cece 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -29,6 +30,8 @@
 import android.view.DisplayInfo;
 import android.view.animation.Animation;
 
+import java.util.function.Consumer;
+
 /**
  * A token that represents a set of wallpaper windows.
  */
@@ -153,6 +156,11 @@
     }
 
     @Override
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        callback.accept(this);
+    }
+
+    @Override
     public String toString() {
         if (stringName == null) {
             StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index e6ac059..729cfc0 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -33,6 +33,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 /**
  * Displays a watermark on top of the window manager's windows.
  */
@@ -47,19 +49,20 @@
     private final int mDeltaY;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
 
-    Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
+    Watermark(Supplier<Surface> surfaceFactory, DisplayContent dc, DisplayMetrics dm,
+            String[] tokens) {
         if (false) {
             Log.i(TAG_WM, "*********************** WATERMARK");
             for (int i=0; i<tokens.length; i++) {
                 Log.i(TAG_WM, "  TOKEN #" + i + ": " + tokens[i]);
             }
         }
-
+        mSurface = surfaceFactory.get();
         mDisplay = dc.getDisplay();
         mTokens = tokens;
 
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index d8ebd84..7c183a8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -19,7 +19,6 @@
 import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.server.wm.AnimationSpecProto.WINDOW;
 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 
 import android.graphics.Point;
@@ -81,11 +80,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return mAnimation.getBackgroundColor();
-    }
-
-    @Override
     public long getDuration() {
         return mAnimation.computeDurationHint();
     }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8db98b..c7916e8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -83,12 +83,13 @@
     private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
     private boolean mInExecuteAfterPrepareSurfacesRunnables;
 
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTransaction;
 
     WindowAnimator(final WindowManagerService service) {
         mService = service;
         mContext = service.mContext;
         mPolicy = service.mPolicy;
+        mTransaction = service.mTransactionFactory.get();
         AnimationThread.getHandler().runWithScissors(
                 () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
 
@@ -179,7 +180,6 @@
                     // Update animations of all applications, including those
                     // associated with exiting/removed apps
                     dc.updateWindowsForAnimator();
-                    dc.updateBackgroundForAnimator();
                     dc.prepareSurfaces();
                 }
 
@@ -305,24 +305,6 @@
         }
     }
 
-    int getPendingLayoutChanges(final int displayId) {
-        if (displayId < 0) {
-            return 0;
-        }
-        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
-        return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
-    }
-
-    void setPendingLayoutChanges(final int displayId, final int changes) {
-        if (displayId < 0) {
-            return;
-        }
-        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
-        if (displayContent != null) {
-            displayContent.pendingLayoutChanges |= changes;
-        }
-    }
-
     private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
         if (displayId < 0) {
             return null;
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
index 775d5b2..d5d4e08 100644
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -70,11 +70,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return 0;
-    }
-
-    @Override
     public long getDuration() {
         return mAnimation.getDuration();
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 29d232f..586375f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -139,7 +139,7 @@
 
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
-        mPendingTransaction = wms.mTransactionFactory.make();
+        mPendingTransaction = wms.mTransactionFactory.get();
         mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
     }
 
@@ -895,6 +895,12 @@
         }
     }
 
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).forAllWallpaperWindows(callback);
+        }
+    }
+
     /**
      * For all tasks at or below this container call the callback.
      *
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 750926f..aa2a964 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -30,6 +30,7 @@
 import android.view.MagnificationSpec;
 import android.view.WindowInfo;
 
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -519,4 +520,10 @@
      */
     public abstract boolean isTouchableDisplay(int displayId);
 
+    /**
+     * Returns the info associated with the input token used to determine if a key should be
+     * intercepted. This info can be accessed without holding the global wm lock.
+     */
+    public abstract @Nullable KeyInterceptionInfo
+            getKeyInterceptionInfoFromToken(IBinder inputToken);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d49d245..8b227a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -28,6 +28,7 @@
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
@@ -36,7 +37,9 @@
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DOCKED_INVALID;
@@ -245,6 +248,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
@@ -262,6 +266,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
+import com.android.server.protolog.ProtoLogImpl;
 import com.android.server.utils.PriorityDump;
 
 import java.io.BufferedWriter;
@@ -281,8 +286,12 @@
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
@@ -292,7 +301,6 @@
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
     static final boolean PROFILE_ORIENTATION = false;
-    static final boolean localLOGV = DEBUG;
 
     /** How much to multiply the policy's type layer, to reserve room
      * for multiple windows of the same type and Z-ordering adjustment
@@ -309,11 +317,6 @@
     static final int WINDOW_LAYER_MULTIPLIER = 5;
 
     /**
-     * Dim surface layer is immediately below target window.
-     */
-    static final int LAYER_OFFSET_DIM = 1;
-
-    /**
      * Animation thumbnail is as far as possible below the window above
      * the thumbnail (or in other words as far as possible above the window
      * below it).
@@ -363,14 +366,28 @@
     private static final String DENSITY_OVERRIDE = "ro.config.density_override";
     private static final String SIZE_OVERRIDE = "ro.config.size_override";
 
-    private static final int MAX_SCREENSHOT_RETRIES = 3;
-
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
 
     // Used to indicate that if there is already a transition set, it should be preserved when
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
+    /**
+     * If set, new app transition framework which supports setting animation on any element
+     * in a surface is used.
+     * <p>
+     * Only set this to non-zero once the new app transition framework is productionalized.
+     * </p>
+     */
+    private static final String HIERARCHICAL_ANIMATIONS_PROPERTY =
+            "persist.wm.hierarchical_animations";
+
+    /**
+     * @see #HIERARCHICAL_ANIMATIONS_PROPERTY
+     */
+    static boolean sHierarchicalAnimations =
+            SystemProperties.getBoolean(HIERARCHICAL_ANIMATIONS_PROPERTY, false);
+
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -394,6 +411,14 @@
     int mVr2dDisplayId = INVALID_DISPLAY;
     boolean mVrModeEnabled = false;
 
+    /**
+     * Tracks a map of input tokens to info that is used to decide whether to intercept
+     * a key event.
+     */
+    final Map<IBinder, KeyInterceptionInfo> mKeyInterceptionInfoForToken =
+            Collections.synchronizedMap(new ArrayMap<>());
+
+
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
@@ -666,7 +691,8 @@
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
     SettingsObserver mSettingsObserver;
 
-    private final class SettingsObserver extends ContentObserver {
+    @VisibleForTesting
+    final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
                 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
         private final Uri mWindowAnimationScaleUri =
@@ -681,6 +707,12 @@
                 Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
         private final Uri mPointerLocationUri =
                 Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
+        private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
+                        Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
+        private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
+                Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT);
+        private final Uri mForceResizableUri = Settings.Global.getUriFor(
+                DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
 
         public SettingsObserver() {
             super(new Handler());
@@ -697,6 +729,10 @@
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -720,6 +756,21 @@
                 return;
             }
 
+            if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
+                updateForceDesktopModeOnExternalDisplays();
+                return;
+            }
+
+            if (mFreeformWindowUri.equals(uri)) {
+                updateFreeformWindowManagement();
+                return;
+            }
+
+            if (mForceResizableUri.equals(uri)) {
+                updateForceResizableTasks();
+                return;
+            }
+
             @UpdateAnimationScaleMode
             final int mode;
             if (mWindowAnimationScaleUri.equals(uri)) {
@@ -762,6 +813,33 @@
                         mPointerLocationEnabled));
             }
         }
+
+        void updateForceDesktopModeOnExternalDisplays() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+            if (mForceDesktopModeOnExternalDisplays == enableForceDesktopMode) {
+                return;
+            }
+            setForceDesktopModeOnExternalDisplays(enableForceDesktopMode);
+        }
+
+        void updateFreeformWindowManagement() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean freeformWindowManagement = mContext.getPackageManager().hasSystemFeature(
+                    FEATURE_FREEFORM_WINDOW_MANAGEMENT) || Settings.Global.getInt(
+                    resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+
+            mAtmService.mSupportsFreeformWindowManagement = freeformWindowManagement;
+        }
+
+        void updateForceResizableTasks() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean forceResizable = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+
+            mAtmService.mForceResizableActivities = forceResizable;
+        }
     }
 
     PowerManager mPowerManager;
@@ -855,9 +933,9 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
-    SurfaceBuilderFactory mSurfaceBuilderFactory = SurfaceControl.Builder::new;
-    TransactionFactory mTransactionFactory = SurfaceControl.Transaction::new;
-    SurfaceFactory mSurfaceFactory = Surface::new;
+    Function<SurfaceSession, SurfaceControl.Builder> mSurfaceControlFactory;
+    Supplier<SurfaceControl.Transaction> mTransactionFactory;
+    final Supplier<Surface> mSurfaceFactory;
 
     private final SurfaceControl.Transaction mTransaction;
 
@@ -957,20 +1035,22 @@
             final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
         return main(context, im, showBootMsgs, onlyCore, policy, atm,
-                SurfaceControl.Transaction::new);
+                SurfaceControl.Transaction::new, Surface::new, SurfaceControl.Builder::new);
     }
 
     /**
      * Creates and returns an instance of the WindowManagerService. This call allows the caller
-     * to override the {@link TransactionFactory} to stub functionality under test.
+     * to override factories that can be used to stub native calls during test.
      */
     @VisibleForTesting
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
-            ActivityTaskManagerService atm, TransactionFactory transactionFactory) {
+            ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
+            Supplier<Surface> surfaceFactory,
+            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
         DisplayThread.getHandler().runWithScissors(() ->
                 sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
-                        atm, transactionFactory), 0);
+                        atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
         return sInstance;
     }
 
@@ -992,7 +1072,9 @@
 
     private WindowManagerService(Context context, InputManagerService inputManager,
             boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,
-            ActivityTaskManagerService atm, TransactionFactory transactionFactory) {
+            ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
+            Supplier<Surface> surfaceFactory,
+            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
         installLock(this, INDEX_WINDOW);
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
@@ -1020,10 +1102,13 @@
                 com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mDisplayWindowSettings = new DisplayWindowSettings(this);
 
+        mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
-        mTransaction = mTransactionFactory.make();
+        mSurfaceFactory = surfaceFactory;
+        mTransaction = mTransactionFactory.get();
+
+        mDisplayWindowSettings = new DisplayWindowSettings(this);
         mPolicy = policy;
         mAnimator = new WindowAnimator(this);
         mRoot = new RootWindowContainer(this);
@@ -1127,7 +1212,8 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
         mHoldingScreenWakeLock.setReferenceCounted(false);
 
-        mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(mTransactionFactory,
+                mPowerManagerInternal);
 
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
@@ -1151,7 +1237,8 @@
         mPropertiesChangedListener = properties -> {
             synchronized (mGlobalLock) {
                 final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
-                        properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
+                        DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                                KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
                 final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
                         DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                         KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
@@ -1605,8 +1692,10 @@
             }
             displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
 
-            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
-                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
+            if (DEBUG || DEBUG_ADD_REMOVE) {
+                Slog.v(TAG_WM, "addWindow: New client " + client.asBinder()
+                        + ": window=" + win + " Callers=" + Debug.getCallers(5));
+            }
 
             if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
                 displayContent.sendNewConfiguration();
@@ -1980,13 +2069,11 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         long origId = Binder.clearCallingIdentity();
-        final int displayId;
         synchronized (mGlobalLock) {
             final WindowState win = windowForClientLocked(session, client, false);
             if (win == null) {
                 return 0;
             }
-            displayId = win.getDisplayId();
             final DisplayContent displayContent = win.getDisplayContent();
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
 
@@ -2273,16 +2360,18 @@
             outCutout.set(win.getWmDisplayCutout().getDisplayCutout());
             outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw()));
             outInsetsState.set(displayContent.getInsetsStateController().getInsetsForDispatch(win));
-            if (localLOGV) Slog.v(
-                TAG_WM, "Relayout given client " + client.asBinder()
-                + ", requestedWidth=" + requestedWidth
-                + ", requestedHeight=" + requestedHeight
-                + ", viewVisibility=" + viewVisibility
-                + "\nRelayout returning frame=" + outFrame
-                + ", surface=" + outSurfaceControl);
+            if (DEBUG) {
+                Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
+                                + ", requestedWidth=" + requestedWidth
+                                + ", requestedHeight=" + requestedHeight
+                                + ", viewVisibility=" + viewVisibility
+                                + "\nRelayout returning frame=" + outFrame
+                                + ", surface=" + outSurfaceControl);
+            }
 
-            if (localLOGV || DEBUG_FOCUS) Slog.v(
-                TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+            if (DEBUG || DEBUG_FOCUS) {
+                Slog.v(TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+            }
 
             result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
 
@@ -2562,16 +2651,14 @@
         getDefaultDisplayContentLocked().executeAppTransition();
     }
 
-    public void initializeRecentsAnimation(int targetActivityType,
+    void initializeRecentsAnimation(int targetActivityType,
             IRecentsAnimationRunner recentsAnimationRunner,
             RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
             SparseBooleanArray recentTaskIds) {
-        synchronized (mGlobalLock) {
-            mRecentsAnimationController = new RecentsAnimationController(this,
-                    recentsAnimationRunner, callbacks, displayId);
-            mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
-            mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
-        }
+        mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
+                callbacks, displayId);
+        mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
+        mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
     }
 
     @VisibleForTesting
@@ -2709,21 +2796,6 @@
     }
 
     /**
-     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
-     * performance, only one layout pass should be done. This can be called multiple times, and
-     * layouting will be resumed once the last caller has called
-     * {@link #continueSurfaceLayout}.
-     */
-    void deferSurfaceLayout() {
-        mWindowPlacerLocked.deferLayout();
-    }
-
-    /** Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()} */
-    void continueSurfaceLayout() {
-        mWindowPlacerLocked.continueLayout();
-    }
-
-    /**
      * Notifies activity manager that some Keyguard flags have changed and that it needs to
      * reevaluate the visibilities of the activities.
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
@@ -2732,12 +2804,6 @@
         mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
     }
 
-    public boolean isKeyguardTrusted() {
-        synchronized (mGlobalLock) {
-            return mPolicy.isKeyguardTrustedLw();
-        }
-    }
-
     public void setKeyguardGoingAway(boolean keyguardGoingAway) {
         synchronized (mGlobalLock) {
             mKeyguardGoingAway = keyguardGoingAway;
@@ -2883,13 +2949,6 @@
         }
     }
 
-    public boolean isShowingDream() {
-        synchronized (mGlobalLock) {
-            // TODO(b/123372519): Fix this when dream can be shown on non-default display.
-            return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
-        }
-    }
-
     @Override
     public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) {
         if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
@@ -2900,12 +2959,6 @@
         }
     }
 
-    public void onKeyguardOccludedChanged(boolean occluded) {
-        synchronized (mGlobalLock) {
-            mPolicy.onKeyguardOccludedChangedLw(occluded);
-        }
-    }
-
     @Override
     public void setSwitchingUser(boolean switching) {
         if (!checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -3404,7 +3457,7 @@
                         int maskThickness = mContext.getResources().getDimensionPixelSize(
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
-                        mCircularDisplayMask = new CircularDisplayMask(
+                        mCircularDisplayMask = new CircularDisplayMask(mSurfaceFactory,
                                 getDefaultDisplayContentLocked(),
                                 mPolicy.getWindowLayerFromTypeLw(
                                         WindowManager.LayoutParams.TYPE_POINTER)
@@ -3432,6 +3485,7 @@
             try {
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
+                            mSurfaceFactory,
                             mContext,
                             getDefaultDisplayContentLocked(),
                             mPolicy.getWindowLayerFromTypeLw(
@@ -3480,7 +3534,7 @@
             try {
                 // TODO(multi-display): support multiple displays
                 if (mStrictModeFlash == null) {
-                    mStrictModeFlash = new StrictModeFlash(
+                    mStrictModeFlash = new StrictModeFlash(mSurfaceFactory,
                             getDefaultDisplayContentLocked());
                 }
                 mStrictModeFlash.setVisibility(on);
@@ -5175,7 +5229,7 @@
 
     final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
         WindowState win = mWindowMap.get(client);
-        if (localLOGV) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
+        if (DEBUG) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
         if (win == null) {
             if (throwOnError) {
                 throw new IllegalArgumentException(
@@ -5275,9 +5329,7 @@
     }
 
     void requestTraversal() {
-        synchronized (mGlobalLock) {
-            mWindowPlacerLocked.requestTraversal();
-        }
+        mWindowPlacerLocked.requestTraversal();
     }
 
     /** Note that Locked in this case is on mLayoutToAnim */
@@ -5497,7 +5549,8 @@
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
                     final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+                    mWatermark = new Watermark(mSurfaceFactory, displayContent,
+                            displayContent.mRealDisplayMetrics,
                             toks);
                 }
             }
@@ -5744,55 +5797,6 @@
         }
     }
 
-    public void notifyAppRelaunching(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
-            if (appWindow != null) {
-                appWindow.startRelaunching();
-            }
-        }
-    }
-
-    public void notifyAppRelaunchingFinished(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
-            if (appWindow != null) {
-                appWindow.finishRelaunching();
-            }
-        }
-    }
-
-    public void notifyAppRelaunchesCleared(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
-            if (appWindow != null) {
-                appWindow.clearRelaunching();
-            }
-        }
-    }
-
-    public void notifyAppResumedFinished(IBinder token) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindow = mRoot.getAppWindowToken(token);
-            if (appWindow != null) {
-                appWindow.getDisplayContent().mUnknownAppVisibilityController
-                        .notifyAppResumedFinished(appWindow);
-            }
-        }
-    }
-
-    /**
-     * Called when a task has been removed from the recent tasks list.
-     * <p>
-     * Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
-     * container may not exist when this happens.
-     */
-    public void notifyTaskRemovedFromRecents(int taskId, int userId) {
-        synchronized (mGlobalLock) {
-            mTaskSnapshotController.notifyTaskRemovedFromRecents(taskId, userId);
-        }
-    }
-
     private void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
         pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
         mPolicy.dump("    ", pw, args);
@@ -5819,6 +5823,11 @@
         pw.print(mWindowTracing.getStatus() + "\n");
     }
 
+    private void dumpLogStatus(PrintWriter pw) {
+        pw.println("WINDOW MANAGER LOGGING (dumpsys window logging)");
+        pw.println(ProtoLogImpl.getSingleInstance().getStatus());
+    }
+
     private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
         pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
         for (int i=0; i<mSessions.size(); i++) {
@@ -6214,6 +6223,9 @@
             } else if ("trace".equals(cmd)) {
                 dumpTraceStatus(pw);
                 return;
+            } else if ("logging".equals(cmd)) {
+                dumpLogStatus(pw);
+                return;
             } else if ("refresh".equals(cmd)) {
                 dumpHighRefreshRateBlacklist(pw);
                 return;
@@ -6275,6 +6287,10 @@
             if (dumpAll) {
                 pw.println(separator);
             }
+            dumpLogStatus(pw);
+            if (dumpAll) {
+                pw.println(separator);
+            }
             dumpHighRefreshRateBlacklist(pw);
         }
     }
@@ -6302,16 +6318,6 @@
         }
     }
 
-    public void onDisplayChanged(int displayId) {
-        synchronized (mGlobalLock) {
-            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            if (displayContent != null) {
-                displayContent.updateDisplayInfo();
-            }
-            mWindowPlacerLocked.requestTraversal();
-        }
-    }
-
     @Override
     public Object getWindowManagerLock() {
         return mGlobalLock;
@@ -6322,21 +6328,18 @@
      * a window.
      * @param token Application token for which the activity will be relaunched.
      */
-    public void setWillReplaceWindow(IBinder token, boolean animate) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null) {
-                Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
-                        + token);
-                return;
-            }
-            if (!appWindowToken.hasContentToDisplay()) {
-                Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
-                        + token);
-                return;
-            }
-            appWindowToken.setWillReplaceWindows(animate);
+    void setWillReplaceWindow(IBinder token, boolean animate) {
+        final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+        if (appWindowToken == null) {
+            Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
+            return;
         }
+        if (!appWindowToken.hasContentToDisplay()) {
+            Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+                    + token);
+            return;
+        }
+        appWindowToken.setWillReplaceWindows(animate);
     }
 
     /**
@@ -6384,19 +6387,17 @@
      * @param token Application token for the activity whose window might be replaced.
      * @param replacing Whether the window is being replaced or not.
      */
-    public void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null) {
-                Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
-                        + token);
-                return;
-            }
-            if (replacing) {
-                scheduleWindowReplacementTimeouts(appWindowToken);
-            } else {
-                appWindowToken.clearWillReplaceWindows();
-            }
+    void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
+        final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+        if (appWindowToken == null) {
+            Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
+                    + token);
+            return;
+        }
+        if (replacing) {
+            scheduleWindowReplacementTimeouts(appWindowToken);
+        } else {
+            appWindowToken.clearWillReplaceWindows();
         }
     }
 
@@ -6418,11 +6419,9 @@
         }
     }
 
-    public void setDockedStackResizing(boolean resizing) {
-        synchronized (mGlobalLock) {
-            getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
-            requestTraversal();
-        }
+    void setDockedStackResizing(boolean resizing) {
+        getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+        requestTraversal();
     }
 
     @Override
@@ -6967,7 +6966,9 @@
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
-            requestTraversal();
+            synchronized (mGlobalLock) {
+                requestTraversal();
+            }
         }
 
         @Override
@@ -7383,6 +7384,11 @@
                         && configuration.touchscreen == Configuration.TOUCHSCREEN_FINGER;
             }
         }
+
+        @Override
+        public @Nullable KeyInterceptionInfo getKeyInterceptionInfoFromToken(IBinder inputToken) {
+            return mKeyInterceptionInfoForToken.get(inputToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7488,7 +7494,7 @@
     }
 
     SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
-        return mSurfaceBuilderFactory.make(s);
+        return mSurfaceControlFactory.apply(s);
     }
 
     /**
@@ -7560,7 +7566,8 @@
             mRoot.forAllDisplays(displayContent ->
                     displayContent.getInputMonitor().updateInputWindowsImmediately());
         }
-        new SurfaceControl.Transaction().syncInputWindows().apply(true);
+
+        mTransactionFactory.get().syncInputWindows().apply(true);
     }
 
     private void waitForAnimationsToComplete() {
@@ -7682,7 +7689,7 @@
         h.replaceTouchableRegionWithCrop(null);
 
         SurfaceSession s = new SurfaceSession();
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        SurfaceControl.Transaction t = mTransactionFactory.get();
         t.setInputWindowInfo(surface, h);
         t.apply();
 
@@ -7691,4 +7698,64 @@
         // InputDispatcher hold the last ref.
         inputChannel.release();
     }
+
+    /** Return whether layer tracing is enabled */
+    public boolean isLayerTracing() {
+        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
+                "isLayerTracing");
+        long token = Binder.clearCallingIdentity();
+        try {
+            Parcel data = null;
+            Parcel reply = null;
+            try {
+                IBinder sf = ServiceManager.getService("SurfaceFlinger");
+                if (sf != null) {
+                    reply = Parcel.obtain();
+                    data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    sf.transact(/* LAYER_TRACE_STATUS_CODE */ 1026, data, reply, 0 /* flags */);
+                    return reply.readBoolean();
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get layer tracing");
+            } finally {
+                if (data != null) {
+                    data.recycle();
+                }
+                if (reply != null) {
+                    reply.recycle();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return false;
+    }
+
+    /** Enable or disable layer tracing */
+    public void setLayerTracing(boolean enabled) {
+        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
+                "setLayerTracing");
+        long token = Binder.clearCallingIdentity();
+        try {
+            Parcel data = null;
+            try {
+                IBinder sf = ServiceManager.getService("SurfaceFlinger");
+                if (sf != null) {
+                    data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    data.writeInt(enabled ? 1 : 0);
+                    sf.transact(/* LAYER_TRACE_CONTROL_CODE */ 1025, data, null, 0 /* flags */);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to set layer tracing");
+            } finally {
+                if (data != null) {
+                    data.recycle();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 7384bb7..e01cbf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -28,6 +28,8 @@
 import android.view.IWindowManager;
 import android.view.Surface;
 
+import com.android.server.protolog.ProtoLogImpl;
+
 import java.io.PrintWriter;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -75,6 +77,8 @@
                     // the output trace file, so the shell gets the correct semantics for where
                     // trace files can be written.
                     return mInternal.mWindowTracing.onShellCommand(this);
+                case "logging":
+                    return ProtoLogImpl.getSingleInstance().onShellCommand(this);
                 case "set-user-rotation":
                     return runSetDisplayUserRotation(pw);
                 case "set-fix-to-user-rotation":
@@ -389,6 +393,8 @@
         if (!IS_USER) {
             pw.println("  tracing (start | stop)");
             pw.println("    Start or stop window tracing.");
+            pw.println("  logging (start | stop | enable | disable | enable-text | disable-text)");
+            pw.println("    Logging settings.");
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 242b116..d7116d8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -23,7 +23,6 @@
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
 import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
-import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
@@ -556,14 +555,7 @@
                 continue;
             }
             ActivityRecord topActivity = task.getTopActivity();
-            if (topActivity == null) {
-                continue;
-            }
-            // If an activity has just been started it will not yet be visible, but
-            // is expected to be soon. We treat this as if it were already visible.
-            // This ensures a subsequent activity can be started even before this one
-            // becomes visible.
-            if (topActivity.visible || topActivity.isState(INITIALIZING)) {
+            if (topActivity != null && topActivity.visible) {
                 return true;
             }
         }
@@ -620,7 +612,7 @@
                 final ActivityStack stack = mPreQTopResumedActivity.getActivityStack();
                 if (stack != null) {
                     stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
-                            null /* resuming */, false /* pauseImmediately */);
+                            activity);
                 }
             }
             mPreQTopResumedActivity = activity;
diff --git a/services/core/java/com/android/server/wm/WindowProcessListener.java b/services/core/java/com/android/server/wm/WindowProcessListener.java
index 23d7a6a..1dade15 100644
--- a/services/core/java/com/android/server/wm/WindowProcessListener.java
+++ b/services/core/java/com/android/server/wm/WindowProcessListener.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 
 /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4900869..0a65e324 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -90,6 +90,7 @@
 import static com.android.server.wm.MoveAnimationSpecProto.DURATION_MS;
 import static com.android.server.wm.MoveAnimationSpecProto.FROM;
 import static com.android.server.wm.MoveAnimationSpecProto.TO;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
@@ -115,7 +116,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
-import static com.android.server.wm.WindowManagerService.localLOGV;
 import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -203,6 +203,7 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
@@ -217,7 +218,8 @@
 import java.util.function.Predicate;
 
 /** A window in the window manager. */
-class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
+class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
+        InsetsControlTarget {
     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
 
     // The minimal size of a window within the usable area of the freeform stack.
@@ -519,11 +521,6 @@
     /** When true this window can be displayed on screens owther than mOwnerUid's */
     private boolean mShowToOwnerOnly;
 
-    // Whether the window was visible when we set the app to invisible last time. WM uses
-    // this as a hint to restore the surface (if available) for early animation next time
-    // the app is brought visible.
-    private boolean mWasVisibleBeforeClientHidden;
-
     // This window will be replaced due to relaunch. This allows window manager
     // to differentiate between simple removal of a window and replacement. In the latter case it
     // will preserve the old window until the new one is drawn.
@@ -637,6 +634,7 @@
     private @Nullable InsetsSourceProvider mInsetProvider;
 
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+    private KeyInterceptionInfo mKeyInterceptionInfo;
 
     void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
             @Rotation int rotation, boolean requested) {
@@ -754,9 +752,10 @@
         mSeq = seq;
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
-        if (localLOGV) Slog.v(
-            TAG, "Window " + this + " client=" + c.asBinder()
-            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        if (DEBUG) {
+            Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+                            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        }
         try {
             c.asBinder().linkToDeath(deathRecipient, 0);
         } catch (RemoteException e) {
@@ -824,7 +823,7 @@
     }
 
     void attach() {
-        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
+        if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
         mSession.windowAddedLocked(mAttrs.packageName);
     }
 
@@ -1124,13 +1123,14 @@
             }
         }
 
-        if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
-                "Resolving (mRequestedWidth="
-                + mRequestedWidth + ", mRequestedheight="
-                + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
-                + "): frame=" + mWindowFrames.mFrame.toShortString()
-                + " " + mWindowFrames.getInsetsInfo()
-                + " " + mAttrs.getTitle());
+        if (DEBUG_LAYOUT || DEBUG) {
+            Slog.v(TAG, "Resolving (mRequestedWidth="
+                            + mRequestedWidth + ", mRequestedheight="
+                            + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+                            + "): frame=" + mWindowFrames.mFrame.toShortString()
+                            + " " + mWindowFrames.getInsetsInfo()
+                            + " " + mAttrs.getTitle());
+        }
     }
 
     // TODO: Look into whether this override is still necessary.
@@ -1279,9 +1279,11 @@
         final boolean dragResizingChanged = isDragResizeChanged()
                 && !isDragResizingChangeReported();
 
-        if (localLOGV) Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
-                + " dragResizingChanged=" + dragResizingChanged
-                + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
+        if (DEBUG) {
+            Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+                    + " dragResizingChanged=" + dragResizingChanged
+                    + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
+        }
 
         // We update mLastFrame always rather than in the conditional with the last inset
         // variables, because mFrameSizeChanged only tracks the width and height changing.
@@ -1978,11 +1980,12 @@
         if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM,
                 "Starting window removed " + this);
 
-        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused())
+        if (DEBUG || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused()) {
             Slog.v(TAG_WM, "Remove " + this + " client="
-                        + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
-                        + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
-                        + Debug.getCallers(5));
+                    + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
+                    + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
+                    + Debug.getCallers(5));
+        }
 
         final long origId = Binder.clearCallingIdentity();
 
@@ -2007,8 +2010,6 @@
             // Visibility of the removed window. Will be used later to update orientation later on.
             boolean wasVisible = false;
 
-            final int displayId = getDisplayId();
-
             // First, see if we need to run an animation. If we do, we have to hold off on removing the
             // window until the animation is done. If the display is frozen, just remove immediately,
             // since the animation wouldn't be seen.
@@ -2219,6 +2220,7 @@
             mClientChannel.dispose();
             mClientChannel = null;
         }
+        mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
         mInputWindowHandle.token = null;
     }
 
@@ -3329,7 +3331,8 @@
         }
     }
 
-    void notifyInsetsControlChanged() {
+    @Override
+    public void notifyInsetsControlChanged() {
         final InsetsStateController stateController =
                 getDisplayContent().getInsetsStateController();
         try {
@@ -4375,8 +4378,9 @@
             return;
         }
 
-        if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG,
-                "Exit animation finished in " + this + ": remove=" + mRemoveOnExit);
+        if (DEBUG || DEBUG_ADD_REMOVE) {
+            Slog.v(TAG, "Exit animation finished in " + this + ": remove=" + mRemoveOnExit);
+        }
 
         mDestroying = true;
 
@@ -5326,4 +5330,15 @@
             proto.end(token);
         }
     }
+
+    KeyInterceptionInfo getKeyInterceptionInfo() {
+        if (mKeyInterceptionInfo == null
+                || mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
+                || mKeyInterceptionInfo.layoutParamsType != getAttrs().type
+                || mKeyInterceptionInfo.windowTitle != getWindowTag()) {
+            mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
+                    getWindowTag().toString());
+        }
+        return mKeyInterceptionInfo;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ae3a10a..c676e72 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -27,6 +27,7 @@
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -159,7 +160,7 @@
      * window is first added or shown, cleared when the callback has been made. */
     boolean mEnteringAnimation;
 
-    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTmpTransaction;
 
     /** The pixel format of the underlying SurfaceControl */
     int mSurfaceFormat;
@@ -246,6 +247,7 @@
         final WindowManagerService service = win.mWmService;
 
         mService = service;
+        mTmpTransaction = service.mTransactionFactory.get();
         mAnimator = service.mAnimator;
         mPolicy = service.mPolicy;
         mContext = service.mContext;
@@ -269,20 +271,17 @@
         if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.isVisibleByPolicy()) {
             // Upon completion of a not-visible to visible status bar animation a relayout is
             // required.
-            if (displayContent != null) {
-                displayContent.setLayoutNeeded();
-            }
+            displayContent.setLayoutNeeded();
         }
         mWin.onExitAnimationDone();
-        final int displayId = mWin.getDisplayId();
-        int pendingLayoutChanges = FINISH_LAYOUT_REDO_ANIM;
+        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
         if (displayContent.mWallpaperController.isWallpaperTarget(mWin)) {
-            pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
-        mAnimator.setPendingLayoutChanges(displayId, pendingLayoutChanges);
-        if (DEBUG_LAYOUT_REPEATS)
+        if (DEBUG_LAYOUT_REPEATS) {
             mService.mWindowPlacerLocked.debugLayoutRepeats(
-                    "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId));
+                    "WindowStateAnimator", displayContent.pendingLayoutChanges);
+        }
 
         if (mWin.mAppToken != null) {
             mWin.mAppToken.updateReportedVisibilityLocked();
@@ -427,10 +426,6 @@
         }
     }
 
-    private int getLayerStack() {
-        return mWin.getDisplayContent().getDisplay().getLayerStack();
-    }
-
     void resetDrawState() {
         mDrawState = DRAW_PENDING;
 
@@ -539,8 +534,10 @@
             return null;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Got surface: " + mSurfaceController
-                + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top);
+        if (DEBUG) {
+            Slog.v(TAG, "Got surface: " + mSurfaceController
+                    + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top);
+        }
 
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
@@ -551,7 +548,7 @@
 
         mLastHidden = true;
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Created surface " + this);
+        if (DEBUG) Slog.v(TAG, "Created surface " + this);
         return mSurfaceController;
     }
 
@@ -744,11 +741,11 @@
                 mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
             }
 
-            if ((DEBUG_ANIM || WindowManagerService.localLOGV)
-                    && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
-                    TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
-                    + " screen=" + (screenAnimation ?
-                            screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            if ((DEBUG_ANIM || DEBUG) && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) {
+                Slog.v(TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+                                + " screen=" + (screenAnimation
+                        ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            }
             return;
         } else if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
             return;
@@ -761,9 +758,10 @@
             return;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(
-                TAG, "computeShownFrameLocked: " + this +
-                " not attached, mAlpha=" + mAlpha);
+        if (DEBUG) {
+            Slog.v(TAG, "computeShownFrameLocked: " + this
+                    + " not attached, mAlpha=" + mAlpha);
+        }
 
         mShownAlpha = mAlpha;
         mHaveMatrix = false;
@@ -1049,7 +1047,7 @@
         // to prevent further updates until buffer latch.
         // We also need to freeze the Surface geometry until a buffer
         // comes in at the new size (normally position and crop are unfrozen).
-        // setGeometryAppliesWithResizeInTransaction accomplishes this for us.
+        // deferTransactionUntil accomplishes this for us.
         if (wasForceScaled && !mForceScaleUntilResize) {
             mSurfaceController.deferTransactionUntil(mSurfaceController.mSurfaceControl,
                     mWin.getFrameNumber());
@@ -1067,8 +1065,7 @@
 
         if (mSurfaceResized) {
             mReportSurfaceResized = true;
-            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
-                    FINISH_LAYOUT_REDO_WALLPAPER);
+            mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
     }
 
@@ -1163,16 +1160,16 @@
                         if (mIsWallpaper) {
                             w.dispatchWallpaperVisibility(true);
                         }
-                        if (!w.getDisplayContent().getLastHasContent()) {
+                        final DisplayContent displayContent = w.getDisplayContent();
+                        if (!displayContent.getLastHasContent()) {
                             // This draw means the difference between unique content and mirroring.
                             // Run another pass through performLayout to set mHasContent in the
                             // LogicalDisplay.
-                            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
-                                    FINISH_LAYOUT_REDO_ANIM);
+                            displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
                             if (DEBUG_LAYOUT_REPEATS) {
                                 mService.mWindowPlacerLocked.debugLayoutRepeats(
                                         "showSurfaceRobustlyLocked " + w,
-                                        mAnimator.getPendingLayoutChanges(w.getDisplayId()));
+                                        displayContent.pendingLayoutChanges);
                             }
                         }
                     } else {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index bcefa8f..6d813d1 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -81,7 +81,7 @@
     private final int mWindowType;
     private final Session mWindowSession;
 
-    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTmpTransaction;
 
     public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format,
             int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
@@ -96,6 +96,7 @@
         final WindowState win = animator.mWin;
         mWindowType = windowType;
         mWindowSession = win.mSession;
+        mTmpTransaction = mService.mTransactionFactory.get();
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
         final SurfaceControl.Builder b = win.makeSurface()
@@ -244,10 +245,6 @@
         }
     }
 
-    void setGeometryAppliesWithResizeInTransaction(boolean recoveringMemory) {
-        mSurfaceControl.setGeometryAppliesWithResize();
-    }
-
     void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
             boolean recoveringMemory) {
         setMatrix(null, dsdx, dtdx, dtdy, dsdy, false);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index cc791787..56f6d4b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,7 +25,6 @@
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
-import android.util.SparseIntArray;
 
 import java.io.PrintWriter;
 
@@ -50,8 +49,8 @@
 
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
-
-    private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
+    /** The number of layout requests when deferring. */
+    private int mDeferredRequests;
 
     private final Runnable mPerformSurfacePlacement;
 
@@ -65,19 +64,38 @@
     }
 
     /**
-     * See {@link WindowManagerService#deferSurfaceLayout()}
+     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+     * performance, only one layout pass should be done. This can be called multiple times, and
+     * layouting will be resumed once the last caller has called {@link #continueLayout}.
      */
     void deferLayout() {
         mDeferDepth++;
     }
 
     /**
-     * See {@link WindowManagerService#continueSurfaceLayout()}
+     * Resumes layout passes after deferring them. If there is a deferred direct invocation of
+     * {@link #performSurfacePlacement} ({@link #mDeferredRequests} > 0), when the defer is
+     * done, it will continue to perform layout.
+     *
+     * @param hasChanges Something has changed. That means whether to call
+     *                   {@link #performSurfacePlacement} when {@link #mDeferDepth} becomes zero.
+     * @see #deferLayout
      */
-    void continueLayout() {
+    void continueLayout(boolean hasChanges) {
         mDeferDepth--;
-        if (mDeferDepth <= 0) {
+        if (mDeferDepth > 0) {
+            return;
+        }
+
+        if (hasChanges || mDeferredRequests > 0) {
+            if (DEBUG) {
+                Slog.i(TAG, "continueLayout hasChanges=" + hasChanges
+                        + " deferredRequests=" + mDeferredRequests + " " + Debug.getCallers(2, 3));
+            }
             performSurfacePlacement();
+            mDeferredRequests = 0;
+        } else if (DEBUG) {
+            Slog.i(TAG, "Cancel continueLayout " + Debug.getCallers(2, 3));
         }
     }
 
@@ -97,6 +115,7 @@
 
     final void performSurfacePlacement(boolean force) {
         if (mDeferDepth > 0 && !force) {
+            mDeferredRequests++;
             return;
         }
         int loopCount = 6;
@@ -195,10 +214,19 @@
     }
 
     void requestTraversal() {
-        if (!mTraversalScheduled) {
-            mTraversalScheduled = true;
-            mService.mAnimationHandler.post(mPerformSurfacePlacement);
+        if (mTraversalScheduled) {
+            return;
         }
+
+        // Set as scheduled even the request will be deferred because mDeferredRequests is also
+        // increased, then the end of deferring will perform the request.
+        mTraversalScheduled = true;
+        if (mDeferDepth > 0) {
+            mDeferredRequests++;
+            if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));
+            return;
+        }
+        mService.mAnimationHandler.post(mPerformSurfacePlacement);
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java
deleted file mode 100644
index 8c65884..0000000
--- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
-
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Queue;
-
-/**
- * Buffer used for window tracing.
- */
-class WindowTraceBuffer {
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    private final Object mBufferLock = new Object();
-
-    private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
-    private int mBufferUsedSize;
-    private int mBufferCapacity;
-
-    WindowTraceBuffer(int bufferCapacity) {
-        mBufferCapacity = bufferCapacity;
-        resetBuffer();
-    }
-
-    int getAvailableSpace() {
-        return mBufferCapacity - mBufferUsedSize;
-    }
-
-    int size() {
-        return mBuffer.size();
-    }
-
-    void setCapacity(int capacity) {
-        mBufferCapacity = capacity;
-    }
-
-    /**
-     * Inserts the specified element into this buffer.
-     *
-     * @param proto the element to add
-     * @throws IllegalStateException if the element cannot be added because it is larger
-     *                               than the buffer size.
-     */
-    void add(ProtoOutputStream proto) {
-        int protoLength = proto.getRawSize();
-        if (protoLength > mBufferCapacity) {
-            throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
-                    + mBufferCapacity + " Object size: " + protoLength);
-        }
-        synchronized (mBufferLock) {
-            discardOldest(protoLength);
-            mBuffer.add(proto);
-            mBufferUsedSize += protoLength;
-            mBufferLock.notify();
-        }
-    }
-
-    boolean contains(byte[] other) {
-        return mBuffer.stream()
-                .anyMatch(p -> Arrays.equals(p.getBytes(), other));
-    }
-
-    /**
-     * Writes the trace buffer to disk.
-     */
-    void writeTraceToFile(File traceFile) throws IOException {
-        synchronized (mBufferLock) {
-            traceFile.delete();
-            try (OutputStream os = new FileOutputStream(traceFile)) {
-                traceFile.setReadable(true /* readable */, false /* ownerOnly */);
-                ProtoOutputStream proto = new ProtoOutputStream();
-                proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
-                os.write(proto.getBytes());
-                for (ProtoOutputStream protoOutputStream : mBuffer) {
-                    proto = protoOutputStream;
-                    byte[] protoBytes = proto.getBytes();
-                    os.write(protoBytes);
-                }
-                os.flush();
-            }
-        }
-    }
-
-    /**
-     * Checks if the element can be added to the buffer. The element is already certain to be
-     * smaller than the overall buffer size.
-     *
-     * @param protoLength byte array representation of the Proto object to add
-     */
-    private void discardOldest(int protoLength) {
-        long availableSpace = getAvailableSpace();
-
-        while (availableSpace < protoLength) {
-
-            ProtoOutputStream item = mBuffer.poll();
-            if (item == null) {
-                throw new IllegalStateException("No element to discard from buffer");
-            }
-            mBufferUsedSize -= item.getRawSize();
-            availableSpace = getAvailableSpace();
-        }
-    }
-
-    /**
-     * Removes all elements form the buffer
-     */
-    void resetBuffer() {
-        synchronized (mBufferLock) {
-            mBuffer.clear();
-            mBufferUsedSize = 0;
-        }
-    }
-
-    @VisibleForTesting
-    int getBufferSize() {
-        return mBufferUsedSize;
-    }
-
-    String getStatus() {
-        synchronized (mBufferLock) {
-            return "Buffer size: "
-                    + mBufferCapacity
-                    + " bytes"
-                    + "\n"
-                    + "Buffer usage: "
-                    + mBufferUsedSize
-                    + " bytes"
-                    + "\n"
-                    + "Elements in the buffer: "
-                    + mBuffer.size();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 765e347..bb66530 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -19,6 +19,9 @@
 import static android.os.Build.IS_USER;
 
 import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
 import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
 import static com.android.server.wm.WindowManagerTraceProto.WHERE;
 import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
@@ -31,6 +34,9 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
+import com.android.server.protolog.ProtoLogImpl;
+import com.android.server.utils.TraceBuffer;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -50,6 +56,7 @@
     private static final int BUFFER_CAPACITY_ALL = 4096 * 1024;
     private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb";
     private static final String TAG = "WindowTracing";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
 
     private final WindowManagerService mService;
     private final Choreographer mChoreographer;
@@ -57,7 +64,7 @@
 
     private final Object mEnabledLock = new Object();
     private final File mTraceFile;
-    private final WindowTraceBuffer mBuffer;
+    private final com.android.server.utils.TraceBuffer mBuffer;
     private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
             log("onFrame" /* where */);
 
@@ -84,7 +91,7 @@
         mService = service;
         mGlobalLock = globalLock;
         mTraceFile = file;
-        mBuffer = new WindowTraceBuffer(bufferCapacity);
+        mBuffer = new TraceBuffer(bufferCapacity);
         setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
     }
 
@@ -94,6 +101,7 @@
             return;
         }
         synchronized (mEnabledLock) {
+            ProtoLogImpl.getSingleInstance().startProtoLog(pw);
             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
             mBuffer.resetBuffer();
             mEnabled = mEnabledLockFree = true;
@@ -132,6 +140,7 @@
                 logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
             }
         }
+        ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile);
     }
 
     private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
@@ -317,6 +326,7 @@
         synchronized (mEnabledLock) {
             writeTraceToFileLocked();
         }
+        ProtoLogImpl.getSingleInstance().writeProtoLogToFile();
     }
 
     private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
@@ -334,11 +344,13 @@
     private void writeTraceToFileLocked() {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
-            mBuffer.writeTraceToFile(mTraceFile);
+            ProtoOutputStream proto = new ProtoOutputStream();
+            proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+            mBuffer.writeTraceToFile(mTraceFile, proto);
         } catch (IOException e) {
             Log.e(TAG, "Unable to write buffer to file", e);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 6218498..d9c7fed 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -57,11 +57,25 @@
     ],
 
     include_dirs: [
-        "bionic/libc/private",
         "frameworks/base/libs",
         "frameworks/native/services",
         "system/gatekeeper/include",
     ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+
+    product_variables: {
+        arc: {
+            exclude_srcs: [
+                "com_android_server_AlarmManagerService.cpp",
+            ],
+            srcs: [
+                ":arctimersrcs",
+            ],
+        }
+    }
 }
 
 cc_defaults {
@@ -98,8 +112,6 @@
         "libGLESv2",
         "libnetutils",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libutils",
         "libhwui",
         "libbpf_android",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 159a496..78b64ca 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,7 +24,7 @@
 #include <sensorservice/SensorService.h>
 #include <sensorservicehidl/SensorManager.h>
 
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 #include <cutils/properties.h>
 #include <utils/Log.h>
diff --git a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
index b53ea92..2b1c83f 100644
--- a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
+++ b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
@@ -22,7 +22,7 @@
 namespace {
 
 static jint runSelfTest(JNIEnv* env, jobject /* clazz */) {
-    return BORINGSSL_self_test();
+    return FIPS_mode();
 }
 
 static const JNINativeMethod methods[] = {
@@ -39,4 +39,4 @@
             env, "com/android/server/devicepolicy/CryptoTestHelper", methods, NELEM(methods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 73bb579..4e04348 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -201,13 +201,8 @@
 sp<ISuspendControlService> getSuspendControl() {
     static std::once_flag suspendControlFlag;
     std::call_once(suspendControlFlag, [](){
-        while(gSuspendControl == nullptr) {
-            sp<IBinder> control =
-                    defaultServiceManager()->getService(String16("suspend_control"));
-            if (control != nullptr) {
-                gSuspendControl = interface_cast<ISuspendControlService>(control);
-            }
-        }
+        gSuspendControl = waitForService<ISuspendControlService>(String16("suspend_control"));
+        LOG_ALWAYS_FATAL_IF(gSuspendControl == nullptr);
     });
     return gSuspendControl;
 }
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 6cd9f2c..9ceb770 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -36,61 +36,33 @@
 #include <linux/fsverity.h>
 #else
 
-// Before fs-verity is upstreamed, use the current snapshot for development.
-// https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git/tree/include/uapi/linux/fsverity.h?h=fsverity
-
 #include <linux/limits.h>
 #include <linux/ioctl.h>
 #include <linux/types.h>
 
+#define FS_VERITY_HASH_ALG_SHA256	1
+
+struct fsverity_enable_arg {
+	__u32 version;
+	__u32 hash_algorithm;
+	__u32 block_size;
+	__u32 salt_size;
+	__u64 salt_ptr;
+	__u32 sig_size;
+	__u32 __reserved1;
+	__u64 sig_ptr;
+	__u64 __reserved2[11];
+};
+
 struct fsverity_digest {
     __u16 digest_algorithm;
     __u16 digest_size; /* input/output */
     __u8 digest[];
 };
 
-#define FS_IOC_ENABLE_VERITY	_IO('f', 133)
+#define FS_IOC_ENABLE_VERITY	_IOW('f', 133, struct fsverity_enable_arg)
 #define FS_IOC_MEASURE_VERITY	_IOWR('f', 134, struct fsverity_digest)
 
-#define FS_VERITY_MAGIC		"FSVerity"
-
-#define FS_VERITY_ALG_SHA256	1
-
-struct fsverity_descriptor {
-    __u8 magic[8];		/* must be FS_VERITY_MAGIC */
-    __u8 major_version;	/* must be 1 */
-    __u8 minor_version;	/* must be 0 */
-    __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */
-    __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */
-    __le16 data_algorithm;	/* hash algorithm for data blocks */
-    __le16 tree_algorithm;	/* hash algorithm for tree blocks */
-    __le32 flags;		/* flags */
-    __le32 __reserved1;	/* must be 0 */
-    __le64 orig_file_size;	/* size of the original file data */
-    __le16 auth_ext_count;	/* number of authenticated extensions */
-    __u8 __reserved2[30];	/* must be 0 */
-};
-
-#define FS_VERITY_EXT_ROOT_HASH		1
-#define FS_VERITY_EXT_PKCS7_SIGNATURE	3
-
-struct fsverity_extension {
-    __le32 length;
-    __le16 type;		/* Type of this extension (see codes above) */
-    __le16 __reserved;	/* Reserved, must be 0 */
-};
-
-struct fsverity_digest_disk {
-    __le16 digest_algorithm;
-    __le16 digest_size;
-    __u8 digest[];
-};
-
-struct fsverity_footer {
-    __le32 desc_reverse_offset;	/* distance to fsverity_descriptor */
-    __u8 magic[8];			/* FS_VERITY_MAGIC */
-} __packed;
-
 #endif
 
 const int kSha256Bytes = 32;
@@ -99,52 +71,24 @@
 
 namespace {
 
-class JavaByteArrayHolder {
-  public:
-    JavaByteArrayHolder(const JavaByteArrayHolder &other) = delete;
-    JavaByteArrayHolder(JavaByteArrayHolder &&other)
-          : mEnv(other.mEnv), mBytes(other.mBytes), mElements(other.mElements) {
-        other.mElements = nullptr;
-    }
-
-    static JavaByteArrayHolder newArray(JNIEnv* env, jsize size) {
-        return JavaByteArrayHolder(env, size);
-    }
-
-    jbyte* getRaw() {
-        return mElements;
-    }
-
-    jbyteArray release() {
-        mEnv->ReleaseByteArrayElements(mBytes, mElements, 0);
-        mElements = nullptr;
-        return mBytes;
-    }
-
-    ~JavaByteArrayHolder() {
-        LOG_ALWAYS_FATAL_IF(mElements != nullptr, "Elements are not released");
-    }
-
-  private:
-    JavaByteArrayHolder(JNIEnv* env, jsize size) {
-        mEnv = env;
-        mBytes = mEnv->NewByteArray(size);
-        mElements = mEnv->GetByteArrayElements(mBytes, nullptr);
-        memset(mElements, 0, size);
-    }
-
-    JNIEnv* mEnv;
-    jbyteArray mBytes;
-    jbyte* mElements;
-};
-
-int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
+int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) {
     const char* path = env->GetStringUTFChars(filePath, nullptr);
     ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
+    env->ReleaseStringUTFChars(filePath, path);
     if (rfd.get() < 0) {
       return errno;
     }
-    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) {
+
+    fsverity_enable_arg arg = {};
+    arg.version = 1;
+    arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+    arg.block_size = 4096;
+    arg.salt_size = 0;
+    arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
+    arg.sig_size = env->GetArrayLength(signature);
+    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature);
+
+    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
       return errno;
     }
     return 0;
@@ -159,6 +103,7 @@
 
     const char* path = env->GetStringUTFChars(filePath, nullptr);
     ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
+    env->ReleaseStringUTFChars(filePath, path);
     if (rfd.get() < 0) {
       return errno;
     }
@@ -168,71 +113,9 @@
     return 0;
 }
 
-jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes);
-    fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii.getRaw());
-
-    data->digest_algorithm = FS_VERITY_ALG_SHA256;
-    data->digest_size = kSha256Bytes;
-    if (env->GetArrayLength(digest) != kSha256Bytes) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid hash size of %d",
-                          env->GetArrayLength(digest));
-        return 0;
-    }
-    const jbyte* src = env->GetByteArrayElements(digest, nullptr);
-    memcpy(data->digest, src, kSha256Bytes);
-
-    return raii.release();
-}
-
-
-jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor));
-    fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii.getRaw());
-
-    memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic));
-    desc->major_version = 1;
-    desc->minor_version = 0;
-    desc->log_data_blocksize = 12;
-    desc->log_tree_blocksize = 12;
-    desc->data_algorithm = FS_VERITY_ALG_SHA256;
-    desc->tree_algorithm = FS_VERITY_ALG_SHA256;
-    desc->flags = 0;
-    desc->orig_file_size = fileSize;
-    desc->auth_ext_count = 1;
-
-    return raii.release();
-}
-
-jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId,
-        jint extensionDataSize) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension));
-    fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii.getRaw());
-
-    ext->length = sizeof(fsverity_extension) + extensionDataSize;
-    ext->type = extensionId;
-
-    return raii.release();
-}
-
-jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */,
-        jint offsetToDescriptorHead) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer));
-    fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii.getRaw());
-
-    footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer);
-    memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic));
-
-    return raii.release();
-}
-
 const JNINativeMethod sMethods[] = {
-    { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity },
+    { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity },
     { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity },
-    { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData },
-    { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor },
-    { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension },
-    { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter },
 };
 
 }  // namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f933b09..555968a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -248,6 +248,7 @@
 import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
@@ -500,6 +501,7 @@
     final UsageStatsManagerInternal mUsageStatsManagerInternal;
     final TelephonyManager mTelephonyManager;
     private final LockPatternUtils mLockPatternUtils;
+    private final LockSettingsInternal mLockSettingsInternal;
     private final DeviceAdminServiceController mDeviceAdminServiceController;
     private final OverlayPackagesProvider mOverlayPackagesProvider;
 
@@ -739,7 +741,6 @@
     final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
 
     @GuardedBy("getLockObject()")
-    final SparseArray<PasswordMetrics> mUserPasswordMetrics = new SparseArray<>();
 
     final Handler mHandler;
     final Handler mBackgroundHandler;
@@ -1112,142 +1113,107 @@
             info.writePoliciesToXml(out);
             out.endTag(null, TAG_POLICIES);
             if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
-                out.startTag(null, TAG_PASSWORD_QUALITY);
-                out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.quality));
-                out.endTag(null, TAG_PASSWORD_QUALITY);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
                 if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LENGTH);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.length));
-                    out.endTag(null, TAG_MIN_PASSWORD_LENGTH);
-                }
-                if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
-                    out.startTag(null, TAG_PASSWORD_HISTORY_LENGTH);
-                    out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength));
-                    out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
                 }
                 if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
-                    out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.upperCase));
-                    out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
                 }
                 if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.lowerCase));
-                    out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
                 }
                 if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LETTERS);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.letters));
-                    out.endTag(null, TAG_MIN_PASSWORD_LETTERS);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
                 }
                 if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
-                    out.startTag(null, TAG_MIN_PASSWORD_NUMERIC);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.numeric));
-                    out.endTag(null, TAG_MIN_PASSWORD_NUMERIC);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
                 }
                 if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
-                    out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.symbols));
-                    out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
                 }
                 if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
-                    out.startTag(null, TAG_MIN_PASSWORD_NONLETTER);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.nonLetter));
-                    out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
                 }
             }
+            if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
+            }
             if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
-                out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
-                out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
-                out.endTag(null, TAG_MAX_TIME_TO_UNLOCK);
+                writeAttributeValueToXml(
+                        out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
             }
             if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
-                out.startTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
-                out.attribute(null, ATTR_VALUE, Long.toString(strongAuthUnlockTimeout));
-                out.endTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
+                writeAttributeValueToXml(
+                        out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
             }
             if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
-                out.startTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
-                out.attribute(null, ATTR_VALUE, Integer.toString(maximumFailedPasswordsForWipe));
-                out.endTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
+                writeAttributeValueToXml(
+                        out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
             }
             if (specifiesGlobalProxy) {
-                out.startTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(specifiesGlobalProxy));
-                out.endTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
+                writeAttributeValueToXml(
+                        out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
                 if (globalProxySpec != null) {
-                    out.startTag(null, TAG_GLOBAL_PROXY_SPEC);
-                    out.attribute(null, ATTR_VALUE, globalProxySpec);
-                    out.endTag(null, TAG_GLOBAL_PROXY_SPEC);
+                    writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
                 }
                 if (globalProxyExclusionList != null) {
-                    out.startTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
-                    out.attribute(null, ATTR_VALUE, globalProxyExclusionList);
-                    out.endTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
+                    writeAttributeValueToXml(
+                            out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
                 }
             }
             if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
-                out.startTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
-                out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationTimeout));
-                out.endTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
             }
             if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
-                out.startTag(null, TAG_PASSWORD_EXPIRATION_DATE);
-                out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationDate));
-                out.endTag(null, TAG_PASSWORD_EXPIRATION_DATE);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
             }
             if (encryptionRequested) {
-                out.startTag(null, TAG_ENCRYPTION_REQUESTED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(encryptionRequested));
-                out.endTag(null, TAG_ENCRYPTION_REQUESTED);
+                writeAttributeValueToXml(
+                        out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
             }
             if (testOnlyAdmin) {
-                out.startTag(null, TAG_TEST_ONLY_ADMIN);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(testOnlyAdmin));
-                out.endTag(null, TAG_TEST_ONLY_ADMIN);
+                writeAttributeValueToXml(
+                        out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
             }
             if (disableCamera) {
-                out.startTag(null, TAG_DISABLE_CAMERA);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableCamera));
-                out.endTag(null, TAG_DISABLE_CAMERA);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CAMERA, disableCamera);
             }
             if (disableCallerId) {
-                out.startTag(null, TAG_DISABLE_CALLER_ID);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId));
-                out.endTag(null, TAG_DISABLE_CALLER_ID);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CALLER_ID, disableCallerId);
             }
             if (disableContactsSearch) {
-                out.startTag(null, TAG_DISABLE_CONTACTS_SEARCH);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableContactsSearch));
-                out.endTag(null, TAG_DISABLE_CONTACTS_SEARCH);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
             }
             if (!disableBluetoothContactSharing) {
-                out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(disableBluetoothContactSharing));
-                out.endTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
             }
             if (disableScreenCapture) {
-                out.startTag(null, TAG_DISABLE_SCREEN_CAPTURE);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableScreenCapture));
-                out.endTag(null, TAG_DISABLE_SCREEN_CAPTURE);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
             }
             if (requireAutoTime) {
-                out.startTag(null, TAG_REQUIRE_AUTO_TIME);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(requireAutoTime));
-                out.endTag(null, TAG_REQUIRE_AUTO_TIME);
+                writeAttributeValueToXml(
+                        out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
             }
             if (forceEphemeralUsers) {
-                out.startTag(null, TAG_FORCE_EPHEMERAL_USERS);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers));
-                out.endTag(null, TAG_FORCE_EPHEMERAL_USERS);
+                writeAttributeValueToXml(
+                        out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
             }
             if (isNetworkLoggingEnabled) {
                 out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
@@ -1259,15 +1225,13 @@
                 out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
             }
             if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
-                out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
-                out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
-                out.endTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
             }
             if (!accountTypesWithManagementDisabled.isEmpty()) {
-                out.startTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT);
                 writeAttributeValuesToXml(
-                        out, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
-                out.endTag(null,  TAG_DISABLE_ACCOUNT_MANAGEMENT);
+                        out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
+                        accountTypesWithManagementDisabled);
             }
             if (!trustAgentInfos.isEmpty()) {
                 Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
@@ -1290,9 +1254,9 @@
                 out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
             }
             if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
-                out.startTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
-                writeAttributeValuesToXml(out, TAG_PROVIDER, crossProfileWidgetProviders);
-                out.endTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
+                writeAttributeValuesToXml(
+                        out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
+                        crossProfileWidgetProviders);
             }
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
@@ -1306,20 +1270,15 @@
                         out, userRestrictions, TAG_USER_RESTRICTIONS);
             }
             if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
-                out.startTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
-                writeAttributeValuesToXml(
-                        out, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
-                out.endTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
+                writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
+                        TAG_RESTRICTION,
+                        defaultEnabledRestrictionsAlreadySet);
             }
             if (!TextUtils.isEmpty(shortSupportMessage)) {
-                out.startTag(null, TAG_SHORT_SUPPORT_MESSAGE);
-                out.text(shortSupportMessage.toString());
-                out.endTag(null, TAG_SHORT_SUPPORT_MESSAGE);
+                writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
             }
             if (!TextUtils.isEmpty(longSupportMessage)) {
-                out.startTag(null, TAG_LONG_SUPPORT_MESSAGE);
-                out.text(longSupportMessage.toString());
-                out.endTag(null, TAG_LONG_SUPPORT_MESSAGE);
+                writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
             }
             if (parentAdmin != null) {
                 out.startTag(null, TAG_PARENT_ADMIN);
@@ -1327,29 +1286,20 @@
                 out.endTag(null, TAG_PARENT_ADMIN);
             }
             if (organizationColor != DEF_ORGANIZATION_COLOR) {
-                out.startTag(null, TAG_ORGANIZATION_COLOR);
-                out.attribute(null, ATTR_VALUE, Integer.toString(organizationColor));
-                out.endTag(null, TAG_ORGANIZATION_COLOR);
+                writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
             }
             if (organizationName != null) {
-                out.startTag(null, TAG_ORGANIZATION_NAME);
-                out.text(organizationName);
-                out.endTag(null, TAG_ORGANIZATION_NAME);
+                writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
             }
             if (isLogoutEnabled) {
-                out.startTag(null, TAG_IS_LOGOUT_ENABLED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled));
-                out.endTag(null, TAG_IS_LOGOUT_ENABLED);
+                writeAttributeValueToXml(
+                        out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
             }
             if (startUserSessionMessage != null) {
-                out.startTag(null, TAG_START_USER_SESSION_MESSAGE);
-                out.text(startUserSessionMessage);
-                out.endTag(null, TAG_START_USER_SESSION_MESSAGE);
+                writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
             }
             if (endUserSessionMessage != null) {
-                out.startTag(null, TAG_END_USER_SESSION_MESSAGE);
-                out.text(endUserSessionMessage);
-                out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
+                writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
             }
             if (mCrossProfileCalendarPackages == null) {
                 out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
@@ -1360,25 +1310,58 @@
             }
         }
 
+        void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
+            out.startTag(null, tag);
+            out.text(text);
+            out.endTag(null, tag);
+        }
+
         void writePackageListToXml(XmlSerializer out, String outerTag,
                 List<String> packageList)
                 throws IllegalArgumentException, IllegalStateException, IOException {
             if (packageList == null) {
                 return;
             }
-
-            out.startTag(null, outerTag);
-            writeAttributeValuesToXml(out, TAG_PACKAGE_LIST_ITEM, packageList);
-            out.endTag(null, outerTag);
+            writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
         }
 
-        void writeAttributeValuesToXml(XmlSerializer out, String tag,
+        void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, value);
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Integer.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Long.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Boolean.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
                 @NonNull Collection<String> values) throws IOException {
+            out.startTag(null, outerTag);
             for (String value : values) {
-                out.startTag(null, tag);
+                out.startTag(null, innerTag);
                 out.attribute(null, ATTR_VALUE, value);
-                out.endTag(null, tag);
+                out.endTag(null, innerTag);
             }
+            out.endTag(null, outerTag);
         }
 
         void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
@@ -1994,6 +1977,14 @@
             return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
         }
 
+        LockSettingsInternal getLockSettingsInternal() {
+            return LocalServices.getService(LockSettingsInternal.class);
+        }
+
+        boolean hasUserSetupCompleted(DevicePolicyData userData) {
+            return userData.mUserSetupComplete;
+        }
+
         boolean isBuildDebuggable() {
             return Build.IS_DEBUGGABLE;
         }
@@ -2233,7 +2224,7 @@
 
         mLocalService = new LocalService();
         mLockPatternUtils = injector.newLockPatternUtils();
-
+        mLockSettingsInternal = injector.getLockSettingsInternal();
         // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false?
         mSecurityLogMonitor = new SecurityLogMonitor(this);
 
@@ -2309,17 +2300,6 @@
     }
 
     /**
-     * Provides PasswordMetrics object corresponding to the given user.
-     * @param userHandle the user for whom to provide metrics.
-     * @return the user password metrics, or {@code null} if none have been associated with
-     * the user yet (for example, if the device has booted but not been unlocked).
-     */
-    @GuardedBy("getLockObject()")
-    PasswordMetrics getUserPasswordMetricsLocked(int userHandle) {
-        return mUserPasswordMetrics.get(userHandle);
-    }
-
-    /**
      * Creates and loads the policy data from xml for data that is shared between
      * various profiles of a user. In contrast to {@link #getUserData(int)}
      * it allows access to data of users other than the calling user.
@@ -2353,9 +2333,6 @@
             if (policy != null) {
                 mUserData.remove(userHandle);
             }
-            if (mUserPasswordMetrics.get(userHandle) != null) {
-                mUserPasswordMetrics.remove(userHandle);
-            }
 
             File policyFile = new File(mInjector.environmentGetUserSystemDirectory(userHandle),
                     DEVICE_POLICIES_XML);
@@ -4183,15 +4160,16 @@
     private void updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) {
         final int credentialOwner = getCredentialOwner(userHandle, parent);
         DevicePolicyData policy = getUserData(credentialOwner);
-        PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
-        if (metrics == null) {
-            metrics = new PasswordMetrics();
+        PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
+        // Update the checkpoint only if the user's password metrics is known
+        if (metrics != null) {
+            final boolean newCheckpoint = isPasswordSufficientForUserWithoutCheckpointLocked(
+                    metrics, userHandle, parent);
+            if (newCheckpoint != policy.mPasswordValidAtLastCheckpoint) {
+                policy.mPasswordValidAtLastCheckpoint = newCheckpoint;
+                saveSettingsLocked(credentialOwner);
+            }
         }
-        policy.mPasswordValidAtLastCheckpoint =
-                isPasswordSufficientForUserWithoutCheckpointLocked(
-                        metrics, userHandle, parent);
-
-        saveSettingsLocked(credentialOwner);
     }
 
     /**
@@ -4766,7 +4744,7 @@
             getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
             int credentialOwner = getCredentialOwner(userHandle, parent);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, userHandle, parent);
         }
@@ -4796,15 +4774,15 @@
             enforceUserUnlocked(targetUser, false);
             int credentialOwner = getCredentialOwner(userHandle, false);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(credentialOwner);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, targetUser, false);
         }
     }
 
     private boolean isActivePasswordSufficientForUserLocked(
-            boolean passwordValidAtLastCheckpoint, PasswordMetrics metrics, int userHandle,
-            boolean parent) {
+            boolean passwordValidAtLastCheckpoint, @Nullable PasswordMetrics metrics,
+            int userHandle, boolean parent) {
         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled() && (metrics == null)) {
             // Before user enters their password for the first time after a reboot, return the
             // value of this flag, which tells us whether the password was valid the last time
@@ -4815,9 +4793,10 @@
         }
 
         if (metrics == null) {
-            // This could happen if the user never had a password set, for example, so
-            // setActivePasswordState has never been called for it.
-            metrics = new PasswordMetrics();
+            // Called on a FBE device when the user password exists but its metrics is unknown.
+            // This shouldn't happen since we enforce the user to be unlocked (which would result
+            // in the metrics known to the framework on a FBE device) at all call sites.
+            throw new IllegalStateException("isActivePasswordSufficient called on FBE-locked user");
         }
 
         return isPasswordSufficientForUserWithoutCheckpointLocked(metrics, userHandle, parent);
@@ -4829,7 +4808,7 @@
      * {@code userId} (or its parent, if {@code parent} is set to {@code true}).
      */
     private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
-            PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
+            @NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
         final int requiredQuality = getPasswordQuality(null, userId, parent);
 
         if (requiredQuality >= PASSWORD_QUALITY_NUMERIC
@@ -4867,7 +4846,7 @@
 
         synchronized (getLockObject()) {
             int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false);
-            PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId);
+            PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(targetUserId);
             return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity();
         }
     }
@@ -5666,7 +5645,7 @@
                     KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid));
             try {
                 IKeyChainService keyChain = keyChainConnection.getService();
-                if (!keyChain.installKeyPair(privKey, cert, chain, alias)) {
+                if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
                     return false;
                 }
                 if (requestAccess) {
@@ -6707,31 +6686,6 @@
         }
     }
 
-    /**
-     * Notify DPMS regarding the metric of the current password. This happens when the user changes
-     * the password, but also when the user just unlocks the keyguard. In comparison,
-     * reportPasswordChanged() is only called when the user changes the password.
-     */
-    @Override
-    public void setActivePasswordState(PasswordMetrics metrics, int userHandle) {
-        if (!mLockPatternUtils.hasSecureLockScreen()) {
-            return;
-        }
-        enforceFullCrossUsersPermission(userHandle);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
-        // If the managed profile doesn't have a separate password, set the metrics to default
-        if (isManagedProfile(userHandle) && !isSeparateProfileChallengeEnabled(userHandle)) {
-            metrics = new PasswordMetrics();
-        }
-
-        validateQualityConstant(metrics.quality);
-        synchronized (getLockObject()) {
-            mUserPasswordMetrics.put(userHandle, metrics);
-        }
-    }
-
     @Override
     public void reportPasswordChanged(@UserIdInt int userId) {
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
@@ -7435,8 +7389,7 @@
 
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
-            mInjector.getIActivityManager().requestBugReport(
-                    ActivityManager.BUGREPORT_OPTION_REMOTE);
+            mInjector.getIActivityManager().requestRemoteBugReport();
 
             mRemoteBugreportServiceIsActive.set(true);
             mRemoteBugreportSharingAccepted.set(false);
@@ -8303,7 +8256,7 @@
         if (!mHasFeature) {
             return true;
         }
-        return getUserData(userHandle).mUserSetupComplete;
+        return mInjector.hasUserSetupCompleted(getUserData(userHandle));
     }
 
     private boolean hasPaired(int userHandle) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 8f8f9f9..1ca96ed 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -1,87 +1,10 @@
-// AIDL interfaces between the core system and the networking mainline module.
-aidl_interface {
-    name: "ipmemorystore-aidl-interfaces",
-    local_include_dir: "java",
-    srcs: [
-        "java/android/net/IIpMemoryStore.aidl",
-        "java/android/net/IIpMemoryStoreCallbacks.aidl",
-        "java/android/net/ipmemorystore/**/*.aidl",
-    ],
-    backend: {
-        ndk: {
-            enabled: false,
-        },
-        cpp: {
-            enabled: false,
-        },
-    },
-    api_dir: "aidl/ipmemorystore",
-    versions: [
-        "1",
-        "2",
-        "3",
-    ],
-}
-
-aidl_interface {
-    name: "networkstack-aidl-interfaces",
-    local_include_dir: "java",
-    include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
-    srcs: [
-        "java/android/net/DhcpResultsParcelable.aidl",
-        "java/android/net/INetworkMonitor.aidl",
-        "java/android/net/INetworkMonitorCallbacks.aidl",
-        "java/android/net/INetworkStackConnector.aidl",
-        "java/android/net/INetworkStackStatusCallback.aidl",
-        "java/android/net/InitialConfigurationParcelable.aidl",
-        "java/android/net/NattKeepalivePacketDataParcelable.aidl",
-        "java/android/net/PrivateDnsConfigParcel.aidl",
-        "java/android/net/ProvisioningConfigurationParcelable.aidl",
-        "java/android/net/TcpKeepalivePacketDataParcelable.aidl",
-        "java/android/net/dhcp/DhcpServingParamsParcel.aidl",
-        "java/android/net/dhcp/IDhcpServer.aidl",
-        "java/android/net/dhcp/IDhcpServerCallbacks.aidl",
-        "java/android/net/ip/IIpClient.aidl",
-        "java/android/net/ip/IIpClientCallbacks.aidl",
-    ],
-    backend: {
-        ndk: {
-            enabled: false,
-        },
-        cpp: {
-            enabled: false,
-        },
-    },
-    api_dir: "aidl/networkstack",
-    imports: ["ipmemorystore-aidl-interfaces"],
-    versions: [
-        "1",
-        "2",
-        "3",
-    ],
-}
-
 java_library_static {
     name: "services.net",
     srcs: ["java/**/*.java"],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
-        "ipmemorystore-client",
         "netd_aidl_interface-java",
-        "networkstack-aidl-interfaces-V3-java",
-    ],
-}
-
-java_library_static {
-    name: "ipmemorystore-client",
-    sdk_version: "system_current",
-    srcs: [
-        ":framework-annotations",
-        "java/android/net/IpMemoryStoreClient.java",
-        "java/android/net/ipmemorystore/**/*.java",
-    ],
-    static_libs: [
-        "ipmemorystore-aidl-interfaces-V3-java",
+        "networkstack-client",
     ],
 }
 
diff --git a/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl b/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl
deleted file mode 100644
index a8cbab2..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStore.aidl
+++ /dev/null
@@ -1,9 +0,0 @@
-package android.net;
-interface IIpMemoryStore {
-  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
-  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
-  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
-  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl
deleted file mode 100644
index cf02c26..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/IIpMemoryStoreCallbacks.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net;
-interface IIpMemoryStoreCallbacks {
-  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl
deleted file mode 100644
index 291dbef..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/Blob.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-parcelable Blob {
-  byte[] data;
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
deleted file mode 100644
index 52f40d4..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnBlobRetrievedListener {
-  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
deleted file mode 100644
index 7853514..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnL2KeyResponseListener {
-  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
deleted file mode 100644
index 3dd2ae6..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnNetworkAttributesRetrievedListener {
-  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
deleted file mode 100644
index 46d4ecb..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnSameL3NetworkResponseListener {
-  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl
deleted file mode 100644
index 54e654b..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/IOnStatusListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnStatusListener {
-  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
deleted file mode 100644
index 9531ea3..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ /dev/null
@@ -1,8 +0,0 @@
-package android.net.ipmemorystore;
-parcelable NetworkAttributesParcelable {
-  byte[] assignedV4Address;
-  long assignedV4AddressExpiry;
-  String groupHint;
-  android.net.ipmemorystore.Blob[] dnsAddresses;
-  int mtu;
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
deleted file mode 100644
index 414272b..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ /dev/null
@@ -1,6 +0,0 @@
-package android.net.ipmemorystore;
-parcelable SameL3NetworkResponseParcelable {
-  String l2Key1;
-  String l2Key2;
-  float confidence;
-}
diff --git a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl
deleted file mode 100644
index 92c6779..0000000
--- a/services/net/aidl/ipmemorystore/1/android/net/ipmemorystore/StatusParcelable.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-parcelable StatusParcelable {
-  int resultCode;
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
deleted file mode 100644
index a8cbab2..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
+++ /dev/null
@@ -1,9 +0,0 @@
-package android.net;
-interface IIpMemoryStore {
-  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
-  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
-  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
-  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
deleted file mode 100644
index cf02c26..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net;
-interface IIpMemoryStoreCallbacks {
-  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
deleted file mode 100644
index 291dbef..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-parcelable Blob {
-  byte[] data;
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
deleted file mode 100644
index 52f40d4..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnBlobRetrievedListener {
-  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
deleted file mode 100644
index 7853514..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnL2KeyResponseListener {
-  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
deleted file mode 100644
index 3dd2ae6..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnNetworkAttributesRetrievedListener {
-  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
deleted file mode 100644
index 46d4ecb..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnSameL3NetworkResponseListener {
-  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
deleted file mode 100644
index 54e654b..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-interface IOnStatusListener {
-  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
deleted file mode 100644
index 9531ea3..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ /dev/null
@@ -1,8 +0,0 @@
-package android.net.ipmemorystore;
-parcelable NetworkAttributesParcelable {
-  byte[] assignedV4Address;
-  long assignedV4AddressExpiry;
-  String groupHint;
-  android.net.ipmemorystore.Blob[] dnsAddresses;
-  int mtu;
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
deleted file mode 100644
index 414272b..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ /dev/null
@@ -1,6 +0,0 @@
-package android.net.ipmemorystore;
-parcelable SameL3NetworkResponseParcelable {
-  String l2Key1;
-  String l2Key2;
-  float confidence;
-}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
deleted file mode 100644
index 92c6779..0000000
--- a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.ipmemorystore;
-parcelable StatusParcelable {
-  int resultCode;
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
deleted file mode 100644
index 30893b2..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStore.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface IIpMemoryStore {
-  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
-  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
-  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
-  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
-  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
-  oneway void factoryReset();
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
deleted file mode 100644
index 535ae2c..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/IIpMemoryStoreCallbacks.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface IIpMemoryStoreCallbacks {
-  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
deleted file mode 100644
index 6d2dc0c..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/Blob.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-parcelable Blob {
-  byte[] data;
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
deleted file mode 100644
index 48c1fb8..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-interface IOnBlobRetrievedListener {
-  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
deleted file mode 100644
index aebc724..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-interface IOnL2KeyResponseListener {
-  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
deleted file mode 100644
index b66db5a..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-interface IOnNetworkAttributesRetrievedListener {
-  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
deleted file mode 100644
index e9f2db4..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-interface IOnSameL3NetworkResponseListener {
-  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
deleted file mode 100644
index 49172ce..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/IOnStatusListener.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-interface IOnStatusListener {
-  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
deleted file mode 100644
index 188db20..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-parcelable NetworkAttributesParcelable {
-  byte[] assignedV4Address;
-  long assignedV4AddressExpiry;
-  String groupHint;
-  android.net.ipmemorystore.Blob[] dnsAddresses;
-  int mtu;
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
deleted file mode 100644
index 7a2ed48..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-parcelable SameL3NetworkResponseParcelable {
-  String l2Key1;
-  String l2Key2;
-  float confidence;
-}
diff --git a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
deleted file mode 100644
index d9b0678..0000000
--- a/services/net/aidl/ipmemorystore/3/android/net/ipmemorystore/StatusParcelable.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ipmemorystore;
-parcelable StatusParcelable {
-  int resultCode;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl
deleted file mode 100644
index 92b5345..0000000
--- a/services/net/aidl/networkstack/1/android/net/DhcpResultsParcelable.aidl
+++ /dev/null
@@ -1,8 +0,0 @@
-package android.net;
-parcelable DhcpResultsParcelable {
-  android.net.StaticIpConfiguration baseConfiguration;
-  int leaseDuration;
-  int mtu;
-  String serverAddress;
-  String vendorInfo;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/1/android/net/INetworkMonitor.aidl
deleted file mode 100644
index b19f522..0000000
--- a/services/net/aidl/networkstack/1/android/net/INetworkMonitor.aidl
+++ /dev/null
@@ -1,17 +0,0 @@
-package android.net;
-interface INetworkMonitor {
-  oneway void start();
-  oneway void launchCaptivePortalApp();
-  oneway void notifyCaptivePortalAppFinished(int response);
-  oneway void setAcceptPartialConnectivity();
-  oneway void forceReevaluation(int uid);
-  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
-  oneway void notifyDnsResponse(int returnCode);
-  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
-  oneway void notifyNetworkDisconnected();
-  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
-  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
-  const int NETWORK_TEST_RESULT_VALID = 0;
-  const int NETWORK_TEST_RESULT_INVALID = 1;
-  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl
deleted file mode 100644
index ee9871d..0000000
--- a/services/net/aidl/networkstack/1/android/net/INetworkMonitorCallbacks.aidl
+++ /dev/null
@@ -1,8 +0,0 @@
-package android.net;
-interface INetworkMonitorCallbacks {
-  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
-  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
-  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
-  oneway void showProvisioningNotification(String action, String packageName);
-  oneway void hideProvisioningNotification();
-}
diff --git a/services/net/aidl/networkstack/1/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/1/android/net/INetworkStackConnector.aidl
deleted file mode 100644
index 7da11e4..0000000
--- a/services/net/aidl/networkstack/1/android/net/INetworkStackConnector.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.net;
-interface INetworkStackConnector {
-  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
-  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
-  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
-  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
-}
diff --git a/services/net/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl
deleted file mode 100644
index f6ca6f7..0000000
--- a/services/net/aidl/networkstack/1/android/net/INetworkStackStatusCallback.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net;
-interface INetworkStackStatusCallback {
-  oneway void onStatusAvailable(int statusCode);
-}
diff --git a/services/net/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl
deleted file mode 100644
index c80a787..0000000
--- a/services/net/aidl/networkstack/1/android/net/InitialConfigurationParcelable.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.net;
-parcelable InitialConfigurationParcelable {
-  android.net.LinkAddress[] ipAddresses;
-  android.net.IpPrefix[] directlyConnectedRoutes;
-  String[] dnsServers;
-  String gateway;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl
deleted file mode 100644
index 2de790b..0000000
--- a/services/net/aidl/networkstack/1/android/net/PrivateDnsConfigParcel.aidl
+++ /dev/null
@@ -1,5 +0,0 @@
-package android.net;
-parcelable PrivateDnsConfigParcel {
-  String hostname;
-  String[] ips;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl
deleted file mode 100644
index 3a6c304..0000000
--- a/services/net/aidl/networkstack/1/android/net/ProvisioningConfigurationParcelable.aidl
+++ /dev/null
@@ -1,15 +0,0 @@
-package android.net;
-parcelable ProvisioningConfigurationParcelable {
-  boolean enableIPv4;
-  boolean enableIPv6;
-  boolean usingMultinetworkPolicyTracker;
-  boolean usingIpReachabilityMonitor;
-  int requestedPreDhcpActionMs;
-  android.net.InitialConfigurationParcelable initialConfig;
-  android.net.StaticIpConfiguration staticIpConfig;
-  android.net.apf.ApfCapabilities apfCapabilities;
-  int provisioningTimeoutMs;
-  int ipv6AddrGenMode;
-  android.net.Network network;
-  String displayName;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index e121c06..0000000
--- a/services/net/aidl/networkstack/1/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.net;
-parcelable TcpKeepalivePacketDataParcelable {
-  byte[] srcAddress;
-  int srcPort;
-  byte[] dstAddress;
-  int dstPort;
-  int seq;
-  int ack;
-  int rcvWnd;
-  int rcvWndScale;
-  int tos;
-  int ttl;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl
deleted file mode 100644
index 67193ae..0000000
--- a/services/net/aidl/networkstack/1/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ /dev/null
@@ -1,11 +0,0 @@
-package android.net.dhcp;
-parcelable DhcpServingParamsParcel {
-  int serverAddr;
-  int serverAddrPrefixLength;
-  int[] defaultRouters;
-  int[] dnsServers;
-  int[] excludedAddrs;
-  long dhcpLeaseTimeSecs;
-  int linkMtu;
-  boolean metered;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl
deleted file mode 100644
index 9143158..0000000
--- a/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServer.aidl
+++ /dev/null
@@ -1,10 +0,0 @@
-package android.net.dhcp;
-interface IDhcpServer {
-  oneway void start(in android.net.INetworkStackStatusCallback cb);
-  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
-  oneway void stop(in android.net.INetworkStackStatusCallback cb);
-  const int STATUS_UNKNOWN = 0;
-  const int STATUS_SUCCESS = 1;
-  const int STATUS_INVALID_ARGUMENT = 2;
-  const int STATUS_UNKNOWN_ERROR = 3;
-}
diff --git a/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl
deleted file mode 100644
index dcc4489..0000000
--- a/services/net/aidl/networkstack/1/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.dhcp;
-interface IDhcpServerCallbacks {
-  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
-}
diff --git a/services/net/aidl/networkstack/1/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/1/android/net/ip/IIpClient.aidl
deleted file mode 100644
index 95a1574..0000000
--- a/services/net/aidl/networkstack/1/android/net/ip/IIpClient.aidl
+++ /dev/null
@@ -1,14 +0,0 @@
-package android.net.ip;
-interface IIpClient {
-  oneway void completedPreDhcpAction();
-  oneway void confirmConfiguration();
-  oneway void readPacketFilterComplete(in byte[] data);
-  oneway void shutdown();
-  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
-  oneway void stop();
-  oneway void setTcpBufferSizes(in String tcpBufferSizes);
-  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
-  oneway void setMulticastFilter(boolean enabled);
-  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
-  oneway void removeKeepalivePacketFilter(int slot);
-}
diff --git a/services/net/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl
deleted file mode 100644
index d6bc808..0000000
--- a/services/net/aidl/networkstack/1/android/net/ip/IIpClientCallbacks.aidl
+++ /dev/null
@@ -1,16 +0,0 @@
-package android.net.ip;
-interface IIpClientCallbacks {
-  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
-  oneway void onPreDhcpAction();
-  oneway void onPostDhcpAction();
-  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
-  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
-  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
-  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
-  oneway void onReachabilityLost(in String logMsg);
-  oneway void onQuit();
-  oneway void installPacketFilter(in byte[] filter);
-  oneway void startReadPacketFilter();
-  oneway void setFallbackMulticastFilter(boolean enabled);
-  oneway void setNeighborDiscoveryOffload(boolean enable);
-}
diff --git a/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
deleted file mode 100644
index 31891de..0000000
--- a/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
+++ /dev/null
@@ -1,9 +0,0 @@
-package android.net;
-parcelable DhcpResultsParcelable {
-  android.net.StaticIpConfiguration baseConfiguration;
-  int leaseDuration;
-  int mtu;
-  String serverAddress;
-  String vendorInfo;
-  String serverHostName;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl
deleted file mode 100644
index 029968b..0000000
--- a/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-package android.net;
-interface INetworkMonitor {
-  oneway void start();
-  oneway void launchCaptivePortalApp();
-  oneway void notifyCaptivePortalAppFinished(int response);
-  oneway void setAcceptPartialConnectivity();
-  oneway void forceReevaluation(int uid);
-  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
-  oneway void notifyDnsResponse(int returnCode);
-  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
-  oneway void notifyNetworkDisconnected();
-  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
-  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
-  const int NETWORK_TEST_RESULT_VALID = 0;
-  const int NETWORK_TEST_RESULT_INVALID = 1;
-  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
-  const int NETWORK_VALIDATION_RESULT_VALID = 1;
-  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
-  const int NETWORK_VALIDATION_PROBE_DNS = 4;
-  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
-  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
-  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
-  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
deleted file mode 100644
index ee9871d..0000000
--- a/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
+++ /dev/null
@@ -1,8 +0,0 @@
-package android.net;
-interface INetworkMonitorCallbacks {
-  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
-  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
-  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
-  oneway void showProvisioningNotification(String action, String packageName);
-  oneway void hideProvisioningNotification();
-}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
deleted file mode 100644
index 7da11e4..0000000
--- a/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.net;
-interface INetworkStackConnector {
-  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
-  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
-  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
-  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
-}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
deleted file mode 100644
index f6ca6f7..0000000
--- a/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net;
-interface INetworkStackStatusCallback {
-  oneway void onStatusAvailable(int statusCode);
-}
diff --git a/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
deleted file mode 100644
index c80a787..0000000
--- a/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.net;
-parcelable InitialConfigurationParcelable {
-  android.net.LinkAddress[] ipAddresses;
-  android.net.IpPrefix[] directlyConnectedRoutes;
-  String[] dnsServers;
-  String gateway;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index 65de883..0000000
--- a/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-package android.net;
-parcelable NattKeepalivePacketDataParcelable {
-  byte[] srcAddress;
-  int srcPort;
-  byte[] dstAddress;
-  int dstPort;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
deleted file mode 100644
index 2de790b..0000000
--- a/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
+++ /dev/null
@@ -1,5 +0,0 @@
-package android.net;
-parcelable PrivateDnsConfigParcel {
-  String hostname;
-  String[] ips;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
deleted file mode 100644
index 3a6c304..0000000
--- a/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
+++ /dev/null
@@ -1,15 +0,0 @@
-package android.net;
-parcelable ProvisioningConfigurationParcelable {
-  boolean enableIPv4;
-  boolean enableIPv6;
-  boolean usingMultinetworkPolicyTracker;
-  boolean usingIpReachabilityMonitor;
-  int requestedPreDhcpActionMs;
-  android.net.InitialConfigurationParcelable initialConfig;
-  android.net.StaticIpConfiguration staticIpConfig;
-  android.net.apf.ApfCapabilities apfCapabilities;
-  int provisioningTimeoutMs;
-  int ipv6AddrGenMode;
-  android.net.Network network;
-  String displayName;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index e121c06..0000000
--- a/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.net;
-parcelable TcpKeepalivePacketDataParcelable {
-  byte[] srcAddress;
-  int srcPort;
-  byte[] dstAddress;
-  int dstPort;
-  int seq;
-  int ack;
-  int rcvWnd;
-  int rcvWndScale;
-  int tos;
-  int ttl;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
deleted file mode 100644
index 67193ae..0000000
--- a/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ /dev/null
@@ -1,11 +0,0 @@
-package android.net.dhcp;
-parcelable DhcpServingParamsParcel {
-  int serverAddr;
-  int serverAddrPrefixLength;
-  int[] defaultRouters;
-  int[] dnsServers;
-  int[] excludedAddrs;
-  long dhcpLeaseTimeSecs;
-  int linkMtu;
-  boolean metered;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
deleted file mode 100644
index 9143158..0000000
--- a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
+++ /dev/null
@@ -1,10 +0,0 @@
-package android.net.dhcp;
-interface IDhcpServer {
-  oneway void start(in android.net.INetworkStackStatusCallback cb);
-  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
-  oneway void stop(in android.net.INetworkStackStatusCallback cb);
-  const int STATUS_UNKNOWN = 0;
-  const int STATUS_SUCCESS = 1;
-  const int STATUS_INVALID_ARGUMENT = 2;
-  const int STATUS_UNKNOWN_ERROR = 3;
-}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
deleted file mode 100644
index dcc4489..0000000
--- a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ /dev/null
@@ -1,4 +0,0 @@
-package android.net.dhcp;
-interface IDhcpServerCallbacks {
-  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
-}
diff --git a/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl
deleted file mode 100644
index 77d5917..0000000
--- a/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl
+++ /dev/null
@@ -1,15 +0,0 @@
-package android.net.ip;
-interface IIpClient {
-  oneway void completedPreDhcpAction();
-  oneway void confirmConfiguration();
-  oneway void readPacketFilterComplete(in byte[] data);
-  oneway void shutdown();
-  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
-  oneway void stop();
-  oneway void setTcpBufferSizes(in String tcpBufferSizes);
-  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
-  oneway void setMulticastFilter(boolean enabled);
-  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
-  oneway void removeKeepalivePacketFilter(int slot);
-  oneway void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
-}
diff --git a/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
deleted file mode 100644
index d6bc808..0000000
--- a/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
+++ /dev/null
@@ -1,16 +0,0 @@
-package android.net.ip;
-interface IIpClientCallbacks {
-  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
-  oneway void onPreDhcpAction();
-  oneway void onPostDhcpAction();
-  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
-  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
-  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
-  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
-  oneway void onReachabilityLost(in String logMsg);
-  oneway void onQuit();
-  oneway void installPacketFilter(in byte[] filter);
-  oneway void startReadPacketFilter();
-  oneway void setFallbackMulticastFilter(boolean enabled);
-  oneway void setNeighborDiscoveryOffload(boolean enable);
-}
diff --git a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
deleted file mode 100644
index 07ff321..0000000
--- a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable DhcpResultsParcelable {
-  android.net.StaticIpConfiguration baseConfiguration;
-  int leaseDuration;
-  int mtu;
-  String serverAddress;
-  String vendorInfo;
-  String serverHostName;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
deleted file mode 100644
index 8aa68bd..0000000
--- a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface INetworkMonitor {
-  oneway void start();
-  oneway void launchCaptivePortalApp();
-  oneway void notifyCaptivePortalAppFinished(int response);
-  oneway void setAcceptPartialConnectivity();
-  oneway void forceReevaluation(int uid);
-  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
-  oneway void notifyDnsResponse(int returnCode);
-  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
-  oneway void notifyNetworkDisconnected();
-  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
-  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
-  const int NETWORK_TEST_RESULT_VALID = 0;
-  const int NETWORK_TEST_RESULT_INVALID = 1;
-  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
-  const int NETWORK_VALIDATION_RESULT_VALID = 1;
-  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
-  const int NETWORK_VALIDATION_PROBE_DNS = 4;
-  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
-  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
-  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
-  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
deleted file mode 100644
index ea93729..0000000
--- a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface INetworkMonitorCallbacks {
-  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
-  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
-  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
-  oneway void showProvisioningNotification(String action, String packageName);
-  oneway void hideProvisioningNotification();
-}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
deleted file mode 100644
index e3a83d1..0000000
--- a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface INetworkStackConnector {
-  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
-  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
-  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
-  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
-}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
deleted file mode 100644
index 3112a08..0000000
--- a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-interface INetworkStackStatusCallback {
-  oneway void onStatusAvailable(int statusCode);
-}
diff --git a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
deleted file mode 100644
index f846b26..0000000
--- a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable InitialConfigurationParcelable {
-  android.net.LinkAddress[] ipAddresses;
-  android.net.IpPrefix[] directlyConnectedRoutes;
-  String[] dnsServers;
-  String gateway;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index de75940..0000000
--- a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable NattKeepalivePacketDataParcelable {
-  byte[] srcAddress;
-  int srcPort;
-  byte[] dstAddress;
-  int dstPort;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
deleted file mode 100644
index cf0fbce9..0000000
--- a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable PrivateDnsConfigParcel {
-  String hostname;
-  String[] ips;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
deleted file mode 100644
index c0f2d4d..0000000
--- a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable ProvisioningConfigurationParcelable {
-  boolean enableIPv4;
-  boolean enableIPv6;
-  boolean usingMultinetworkPolicyTracker;
-  boolean usingIpReachabilityMonitor;
-  int requestedPreDhcpActionMs;
-  android.net.InitialConfigurationParcelable initialConfig;
-  android.net.StaticIpConfiguration staticIpConfig;
-  android.net.apf.ApfCapabilities apfCapabilities;
-  int provisioningTimeoutMs;
-  int ipv6AddrGenMode;
-  android.net.Network network;
-  String displayName;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index 5926794..0000000
--- a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net;
-parcelable TcpKeepalivePacketDataParcelable {
-  byte[] srcAddress;
-  int srcPort;
-  byte[] dstAddress;
-  int dstPort;
-  int seq;
-  int ack;
-  int rcvWnd;
-  int rcvWndScale;
-  int tos;
-  int ttl;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
deleted file mode 100644
index 7ab156f..0000000
--- a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.dhcp;
-parcelable DhcpServingParamsParcel {
-  int serverAddr;
-  int serverAddrPrefixLength;
-  int[] defaultRouters;
-  int[] dnsServers;
-  int[] excludedAddrs;
-  long dhcpLeaseTimeSecs;
-  int linkMtu;
-  boolean metered;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
deleted file mode 100644
index d281ecf..0000000
--- a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.dhcp;
-interface IDhcpServer {
-  oneway void start(in android.net.INetworkStackStatusCallback cb);
-  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
-  oneway void stop(in android.net.INetworkStackStatusCallback cb);
-  const int STATUS_UNKNOWN = 0;
-  const int STATUS_SUCCESS = 1;
-  const int STATUS_INVALID_ARGUMENT = 2;
-  const int STATUS_UNKNOWN_ERROR = 3;
-}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
deleted file mode 100644
index 98be0ab..0000000
--- a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.dhcp;
-interface IDhcpServerCallbacks {
-  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
-}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
deleted file mode 100644
index 85c8676..0000000
--- a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ip;
-interface IIpClient {
-  oneway void completedPreDhcpAction();
-  oneway void confirmConfiguration();
-  oneway void readPacketFilterComplete(in byte[] data);
-  oneway void shutdown();
-  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
-  oneway void stop();
-  oneway void setTcpBufferSizes(in String tcpBufferSizes);
-  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
-  oneway void setMulticastFilter(boolean enabled);
-  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
-  oneway void removeKeepalivePacketFilter(int slot);
-  oneway void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
-  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
-}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
deleted file mode 100644
index 7fe39ed..0000000
--- a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a frozen snapshot of an AIDL interface (or parcelable). Do not
-// try to edit this file. It looks like you are doing that because you have
-// modified an AIDL interface in a backward-incompatible way, e.g., deleting a
-// function from an interface or a field from a parcelable and it broke the
-// build. That breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.net.ip;
-interface IIpClientCallbacks {
-  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
-  oneway void onPreDhcpAction();
-  oneway void onPostDhcpAction();
-  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
-  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
-  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
-  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
-  oneway void onReachabilityLost(in String logMsg);
-  oneway void onQuit();
-  oneway void installPacketFilter(in byte[] filter);
-  oneway void startReadPacketFilter();
-  oneway void setFallbackMulticastFilter(boolean enabled);
-  oneway void setNeighborDiscoveryOffload(boolean enable);
-}
diff --git a/services/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java
index 7333f58..62f2c35 100644
--- a/services/net/java/android/net/ConnectivityModuleConnector.java
+++ b/services/net/java/android/net/ConnectivityModuleConnector.java
@@ -38,6 +38,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -76,8 +77,17 @@
     private final SharedLog mLog = new SharedLog(TAG);
     @GuardedBy("mHealthListeners")
     private final ArraySet<ConnectivityModuleHealthListener> mHealthListeners = new ArraySet<>();
+    @NonNull
+    private final Dependencies mDeps;
 
-    private ConnectivityModuleConnector() { }
+    private ConnectivityModuleConnector() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    ConnectivityModuleConnector(@NonNull Dependencies deps) {
+        mDeps = deps;
+    }
 
     /**
      * Get the {@link ConnectivityModuleConnector} singleton instance.
@@ -124,6 +134,59 @@
         void onModuleServiceConnected(@NonNull IBinder iBinder);
     }
 
+    /**
+     * Interface used to determine the intent to use to bind to the module. Useful for testing.
+     */
+    @VisibleForTesting
+    protected interface Dependencies {
+        /**
+         * Determine the intent to use to bind to the module.
+         * @return null if the intent could not be resolved, the intent otherwise.
+         */
+        @Nullable
+        Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess);
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Nullable
+        @Override
+        public Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess) {
+            final Intent intent =
+                    new Intent(inSystemProcess
+                            ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
+                            : serviceIntentBaseAction);
+            final ComponentName comp = intent.resolveSystemService(pm, 0);
+            if (comp == null) {
+                return null;
+            }
+            intent.setComponent(comp);
+
+            final int uid;
+            try {
+                uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new SecurityException(
+                        "Could not check network stack UID; package not found.", e);
+            }
+
+            final int expectedUid =
+                    inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
+            if (uid != expectedUid) {
+                throw new SecurityException("Invalid network stack UID: " + uid);
+            }
+
+            if (!inSystemProcess) {
+                checkModuleServicePermission(pm, comp, servicePermissionName);
+            }
+
+            return intent;
+        }
+    }
+
 
     /**
      * Add a {@link ConnectivityModuleHealthListener} to listen to network stack health events.
@@ -156,13 +219,13 @@
         final PackageManager pm = mContext.getPackageManager();
 
         // Try to bind in-process if the device was shipped with an in-process version
-        Intent intent = getModuleServiceIntent(pm, serviceIntentBaseAction, servicePermissionName,
-                true /* inSystemProcess */);
+        Intent intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                servicePermissionName, true /* inSystemProcess */);
 
         // Otherwise use the updatable module version
         if (intent == null) {
-            intent = getModuleServiceIntent(pm, serviceIntentBaseAction, servicePermissionName,
-                false /* inSystemProcess */);
+            intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                    servicePermissionName, false /* inSystemProcess */);
             log("Starting networking module in network_stack process");
         } else {
             log("Starting networking module in system_server process");
@@ -219,41 +282,7 @@
         }
     }
 
-    @Nullable
-    private Intent getModuleServiceIntent(
-            @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
-            @NonNull String servicePermissionName, boolean inSystemProcess) {
-        final Intent intent =
-                new Intent(inSystemProcess
-                        ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
-                        : serviceIntentBaseAction);
-        final ComponentName comp = intent.resolveSystemService(pm, 0);
-        if (comp == null) {
-            return null;
-        }
-        intent.setComponent(comp);
-
-        int uid = -1;
-        try {
-            uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
-        } catch (PackageManager.NameNotFoundException e) {
-            logWtf("Networking module package not found", e);
-            // Fall through
-        }
-
-        final int expectedUid = inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
-        if (uid != expectedUid) {
-            throw new SecurityException("Invalid network stack UID: " + uid);
-        }
-
-        if (!inSystemProcess) {
-            checkModuleServicePermission(pm, comp, servicePermissionName);
-        }
-
-        return intent;
-    }
-
-    private void checkModuleServicePermission(
+    private static void checkModuleServicePermission(
             @NonNull PackageManager pm, @NonNull ComponentName comp,
             @NonNull String servicePermissionName) {
         final int hasPermission =
diff --git a/services/net/java/android/net/DhcpResultsParcelable.aidl b/services/net/java/android/net/DhcpResultsParcelable.aidl
deleted file mode 100644
index c98d9c2..0000000
--- a/services/net/java/android/net/DhcpResultsParcelable.aidl
+++ /dev/null
@@ -1,28 +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 perNmissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.StaticIpConfiguration;
-
-parcelable DhcpResultsParcelable {
-    StaticIpConfiguration baseConfiguration;
-    int leaseDuration;
-    int mtu;
-    String serverAddress;
-    String vendorInfo;
-    String serverHostName;
-}
diff --git a/services/net/java/android/net/IIpMemoryStore.aidl b/services/net/java/android/net/IIpMemoryStore.aidl
deleted file mode 100644
index add221a..0000000
--- a/services/net/java/android/net/IIpMemoryStore.aidl
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.NetworkAttributesParcelable;
-import android.net.ipmemorystore.IOnBlobRetrievedListener;
-import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
-import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
-import android.net.ipmemorystore.IOnStatusListener;
-
-/** {@hide} */
-oneway interface IIpMemoryStore {
-    /**
-     * Store network attributes for a given L2 key.
-     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
-     * calling findL2Key with the attributes and storing in the returned value.
-     *
-     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
-     *              key and only care about grouping can pass a unique ID here like the ones
-     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
-     *              relevance of such a network will lead to it being evicted soon if it's not
-     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
-     * @param attributes The attributes for this network.
-     * @param listener A listener that will be invoked to inform of the completion of this call,
-     *                 or null if the client is not interested in learning about success/failure.
-     * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
-     *         If the call failed, the L2 key will be null.
-     */
-    void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
-            IOnStatusListener listener);
-
-    /**
-     * Store a binary blob associated with an L2 key and a name.
-     *
-     * @param l2Key The L2 key for this network.
-     * @param clientId The ID of the client.
-     * @param name The name of this data.
-     * @param data The data to store.
-     * @param listener A listener to inform of the completion of this call, or null if the client
-     *        is not interested in learning about success/failure.
-     * @return (through the listener) A status to indicate success or failure.
-     */
-    void storeBlob(String l2Key, String clientId, String name, in Blob data,
-            IOnStatusListener listener);
-
-    /**
-     * Returns the best L2 key associated with the attributes.
-     *
-     * This will find a record that would be in the same group as the passed attributes. This is
-     * useful to choose the key for storing a sample or private data when the L2 key is not known.
-     * If multiple records are group-close to these attributes, the closest match is returned.
-     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
-     * order) L2 key is returned.
-     * If no record matches these attributes, null is returned.
-     *
-     * @param attributes The attributes of the network to find.
-     * @param listener The listener that will be invoked to return the answer.
-     * @return (through the listener) The L2 key if one matched, or null.
-     */
-    void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
-
-    /**
-     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
-     * to the same L3 network. Group-closeness is used to determine this.
-     *
-     * @param l2Key1 The key for the first network.
-     * @param l2Key2 The key for the second network.
-     * @param listener The listener that will be invoked to return the answer.
-     * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
-     */
-    void isSameNetwork(String l2Key1, String l2Key2, IOnSameL3NetworkResponseListener listener);
-
-    /**
-     * Retrieve the network attributes for a key.
-     * If no record is present for this key, this will return null attributes.
-     *
-     * @param l2Key The key of the network to query.
-     * @param listener The listener that will be invoked to return the answer.
-     * @return (through the listener) The network attributes and the L2 key associated with
-     *         the query.
-     */
-    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrievedListener listener);
-
-    /**
-     * Retrieve previously stored private data.
-     * If no data was stored for this L2 key and name this will return null.
-     *
-     * @param l2Key The L2 key.
-     * @param clientId The id of the client that stored this data.
-     * @param name The name of the data.
-     * @param listener The listener that will be invoked to return the answer.
-     * @return (through the listener) The private data (or null), with the L2 key
-     *         and the name of the data associated with the query.
-     */
-    void retrieveBlob(String l2Key, String clientId, String name,
-            IOnBlobRetrievedListener listener);
-
-    /**
-     * Delete all data because a factory reset operation is in progress.
-     */
-    void factoryReset();
-}
diff --git a/services/net/java/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/java/android/net/IIpMemoryStoreCallbacks.aidl
deleted file mode 100644
index 53108db..0000000
--- a/services/net/java/android/net/IIpMemoryStoreCallbacks.aidl
+++ /dev/null
@@ -1,24 +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 android.net;
-
-import android.net.IIpMemoryStore;
-
-/** {@hide} */
-oneway interface IIpMemoryStoreCallbacks {
-    void onIpMemoryStoreFetched(in IIpMemoryStore ipMemoryStore);
-}
diff --git a/services/net/java/android/net/INetworkMonitor.aidl b/services/net/java/android/net/INetworkMonitor.aidl
deleted file mode 100644
index 3fc81a3..0000000
--- a/services/net/java/android/net/INetworkMonitor.aidl
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing perNmissions and
- * limitations under the License.
- */
-package android.net;
-
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.PrivateDnsConfigParcel;
-
-/** @hide */
-oneway interface INetworkMonitor {
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should be used as a default internet connection.  It was found to be:
-    // 1. a functioning network providing internet access, or
-    // 2. a captive portal and the user decided to use it as is.
-    const int NETWORK_TEST_RESULT_VALID = 0;
-
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should not be used as a default internet connection.  It was found to be:
-    // 1. a captive portal and the user is prompted to sign-in, or
-    // 2. a captive portal and the user did not want to use it, or
-    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
-    const int NETWORK_TEST_RESULT_INVALID = 1;
-
-    // After a network has been tested, this result can be sent with EVENT_NETWORK_TESTED.
-    // The network may be used as a default internet connection, but it was found to be a partial
-    // connectivity network which can get the pass result for http probe but get the failed result
-    // for https probe.
-    const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
-
-    // Network validation flags indicate probe result and types. If no NETWORK_VALIDATION_RESULT_*
-    // are set, then it's equal to NETWORK_TEST_RESULT_INVALID. If NETWORK_VALIDATION_RESULT_VALID
-    // is set, then the network validates and equal to NETWORK_TEST_RESULT_VALID. If
-    // NETWORK_VALIDATION_RESULT_PARTIAL is set, then the network has partial connectivity which
-    // is equal to NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY. NETWORK_VALIDATION_PROBE_* is set
-    // when the specific probe result of the network is resolved.
-    const int NETWORK_VALIDATION_RESULT_VALID = 0x01;
-    const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
-    const int NETWORK_VALIDATION_PROBE_DNS = 0x04;
-    const int NETWORK_VALIDATION_PROBE_HTTP = 0x08;
-    const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10;
-    const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20;
-    const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40;
-
-    void start();
-    void launchCaptivePortalApp();
-    void notifyCaptivePortalAppFinished(int response);
-    void setAcceptPartialConnectivity();
-    void forceReevaluation(int uid);
-    void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
-    void notifyDnsResponse(int returnCode);
-    void notifyNetworkConnected(in LinkProperties lp, in NetworkCapabilities nc);
-    void notifyNetworkDisconnected();
-    void notifyLinkPropertiesChanged(in LinkProperties lp);
-    void notifyNetworkCapabilitiesChanged(in NetworkCapabilities nc);
-}
diff --git a/services/net/java/android/net/INetworkMonitorCallbacks.aidl b/services/net/java/android/net/INetworkMonitorCallbacks.aidl
deleted file mode 100644
index 2c61511..0000000
--- a/services/net/java/android/net/INetworkMonitorCallbacks.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.INetworkMonitor;
-import android.net.PrivateDnsConfigParcel;
-
-/** @hide */
-oneway interface INetworkMonitorCallbacks {
-    void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
-    void notifyNetworkTested(int testResult, @nullable String redirectUrl);
-    void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
-    void showProvisioningNotification(String action, String packageName);
-    void hideProvisioningNotification();
-}
\ No newline at end of file
diff --git a/services/net/java/android/net/INetworkStackConnector.aidl b/services/net/java/android/net/INetworkStackConnector.aidl
deleted file mode 100644
index 3751c36..0000000
--- a/services/net/java/android/net/INetworkStackConnector.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing perNmissions and
- * limitations under the License.
- */
-package android.net;
-
-import android.net.IIpMemoryStoreCallbacks;
-import android.net.INetworkMonitorCallbacks;
-import android.net.Network;
-import android.net.dhcp.DhcpServingParamsParcel;
-import android.net.dhcp.IDhcpServerCallbacks;
-import android.net.ip.IIpClientCallbacks;
-
-/** @hide */
-oneway interface INetworkStackConnector {
-    void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
-        in IDhcpServerCallbacks cb);
-    void makeNetworkMonitor(in Network network, String name, in INetworkMonitorCallbacks cb);
-    void makeIpClient(in String ifName, in IIpClientCallbacks callbacks);
-    void fetchIpMemoryStore(in IIpMemoryStoreCallbacks cb);
-}
diff --git a/services/net/java/android/net/INetworkStackStatusCallback.aidl b/services/net/java/android/net/INetworkStackStatusCallback.aidl
deleted file mode 100644
index 51032d8..0000000
--- a/services/net/java/android/net/INetworkStackStatusCallback.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-/** @hide */
-oneway interface INetworkStackStatusCallback {
-    void onStatusAvailable(int statusCode);
-}
\ No newline at end of file
diff --git a/services/net/java/android/net/InitialConfigurationParcelable.aidl b/services/net/java/android/net/InitialConfigurationParcelable.aidl
deleted file mode 100644
index 3fa88c3..0000000
--- a/services/net/java/android/net/InitialConfigurationParcelable.aidl
+++ /dev/null
@@ -1,27 +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 android.net;
-
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-
-parcelable InitialConfigurationParcelable {
-    LinkAddress[] ipAddresses;
-    IpPrefix[] directlyConnectedRoutes;
-    String[] dnsServers;
-    String gateway;
-}
\ No newline at end of file
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
deleted file mode 100644
index 014b528..0000000
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ /dev/null
@@ -1,227 +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 android.net;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.NetworkAttributes;
-import android.net.ipmemorystore.OnBlobRetrievedListener;
-import android.net.ipmemorystore.OnL2KeyResponseListener;
-import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
-import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
-import android.net.ipmemorystore.OnStatusListener;
-import android.net.ipmemorystore.Status;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * service used to communicate with the ip memory store service in network stack,
- * which is running in a separate module.
- * @hide
- */
-public abstract class IpMemoryStoreClient {
-    private static final String TAG = IpMemoryStoreClient.class.getSimpleName();
-    private final Context mContext;
-
-    public IpMemoryStoreClient(@NonNull final Context context) {
-        if (context == null) throw new IllegalArgumentException("missing context");
-        mContext = context;
-    }
-
-    protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
-            throws ExecutionException;
-
-    @FunctionalInterface
-    private interface ThrowingRunnable {
-        void run() throws RemoteException;
-    }
-
-    private void ignoringRemoteException(ThrowingRunnable r) {
-        ignoringRemoteException("Failed to execute remote procedure call", r);
-    }
-
-    private void ignoringRemoteException(String message, ThrowingRunnable r) {
-        try {
-            r.run();
-        } catch (RemoteException e) {
-            Log.e(TAG, message, e);
-        }
-    }
-
-    /**
-     * Store network attributes for a given L2 key.
-     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
-     * calling findL2Key with the attributes and storing in the returned value.
-     *
-     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
-     *              key and only care about grouping can pass a unique ID here like the ones
-     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
-     *              relevance of such a network will lead to it being evicted soon if it's not
-     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
-     * @param attributes The attributes for this network.
-     * @param listener A listener that will be invoked to inform of the completion of this call,
-     *                 or null if the client is not interested in learning about success/failure.
-     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
-     * If the call failed, the L2 key will be null.
-     */
-    public void storeNetworkAttributes(@NonNull final String l2Key,
-            @NonNull final NetworkAttributes attributes,
-            @Nullable final OnStatusListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
-                            OnStatusListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error storing network attributes",
-                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
-        }
-    }
-
-    /**
-     * Store a binary blob associated with an L2 key and a name.
-     *
-     * @param l2Key The L2 key for this network.
-     * @param clientId The ID of the client.
-     * @param name The name of this data.
-     * @param data The data to store.
-     * @param listener A listener to inform of the completion of this call, or null if the client
-     *        is not interested in learning about success/failure.
-     * Through the listener, returns a status to indicate success or failure.
-     */
-    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
-            @NonNull final String name, @NonNull final Blob data,
-            @Nullable final OnStatusListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.storeBlob(l2Key, clientId, name, data,
-                            OnStatusListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error storing blob",
-                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
-        }
-    }
-
-    /**
-     * Returns the best L2 key associated with the attributes.
-     *
-     * This will find a record that would be in the same group as the passed attributes. This is
-     * useful to choose the key for storing a sample or private data when the L2 key is not known.
-     * If multiple records are group-close to these attributes, the closest match is returned.
-     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
-     * order) L2 key is returned.
-     * If no record matches these attributes, null is returned.
-     *
-     * @param attributes The attributes of the network to find.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the L2 key if one matched, or null.
-     */
-    public void findL2Key(@NonNull final NetworkAttributes attributes,
-            @NonNull final OnL2KeyResponseListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.findL2Key(attributes.toParcelable(),
-                            OnL2KeyResponseListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error finding L2 Key",
-                    () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
-        }
-    }
-
-    /**
-     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
-     * to the same L3 network. Group-closeness is used to determine this.
-     *
-     * @param l2Key1 The key for the first network.
-     * @param l2Key2 The key for the second network.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
-     */
-    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
-            @NonNull final OnSameL3NetworkResponseListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.isSameNetwork(l2Key1, l2Key2,
-                            OnSameL3NetworkResponseListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error checking for network sameness",
-                    () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
-        }
-    }
-
-    /**
-     * Retrieve the network attributes for a key.
-     * If no record is present for this key, this will return null attributes.
-     *
-     * @param l2Key The key of the network to query.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the network attributes and the L2 key associated with
-     *         the query.
-     */
-    public void retrieveNetworkAttributes(@NonNull final String l2Key,
-            @NonNull final OnNetworkAttributesRetrievedListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.retrieveNetworkAttributes(l2Key,
-                            OnNetworkAttributesRetrievedListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error retrieving network attributes",
-                    () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
-                            null, null));
-        }
-    }
-
-    /**
-     * Retrieve previously stored private data.
-     * If no data was stored for this L2 key and name this will return null.
-     *
-     * @param l2Key The L2 key.
-     * @param clientId The id of the client that stored this data.
-     * @param name The name of the data.
-     * @param listener The listener that will be invoked to return the answer.
-     * Through the listener, returns the private data (or null), with the L2 key
-     *         and the name of the data associated with the query.
-     */
-    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
-            @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.retrieveBlob(l2Key, clientId, name,
-                            OnBlobRetrievedListener.toAIDL(listener))));
-        } catch (ExecutionException m) {
-            ignoringRemoteException("Error retrieving blob",
-                    () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
-                            null, null, null));
-        }
-    }
-
-    /**
-     * Wipe the data in the database upon network factory reset.
-     */
-    public void factoryReset() {
-        try {
-            runWhenServiceReady(service -> ignoringRemoteException(
-                    () -> service.factoryReset()));
-        } catch (ExecutionException m) {
-            Log.e(TAG, "Error executing factory reset", m);
-        }
-    }
-}
diff --git a/services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index 6f006d4..0000000
--- a/services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,25 +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 android.net;
-
-parcelable NattKeepalivePacketDataParcelable {
-    byte[] srcAddress;
-    int srcPort;
-    byte[] dstAddress;
-    int dstPort;
-}
-
diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java
index abb4666..69e2406 100644
--- a/services/net/java/android/net/NetworkStackClient.java
+++ b/services/net/java/android/net/NetworkStackClient.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -51,6 +52,9 @@
     private static NetworkStackClient sInstance;
 
     @NonNull
+    private final Dependencies mDependencies;
+
+    @NonNull
     @GuardedBy("mPendingNetStackRequests")
     private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
     @Nullable
@@ -66,7 +70,50 @@
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
 
-    private NetworkStackClient() { }
+    @VisibleForTesting
+    protected NetworkStackClient(@NonNull Dependencies dependencies) {
+        mDependencies = dependencies;
+    }
+
+    private NetworkStackClient() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    protected interface Dependencies {
+        void addToServiceManager(@NonNull IBinder service);
+        void checkCallerUid();
+        ConnectivityModuleConnector getConnectivityModuleConnector();
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Override
+        public void addToServiceManager(@NonNull IBinder service) {
+            ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service,
+                    false /* allowIsolated */, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        }
+
+        @Override
+        public void checkCallerUid() {
+            final int caller = Binder.getCallingUid();
+            // This is a client lib so "caller" is the current UID in most cases. The check is done
+            // here in the caller's process just to provide a nicer error message to clients; more
+            // generic checks are also done in NetworkStackService.
+            // See PermissionUtil in NetworkStack for the actual check on the service side - the
+            // checks here should be kept in sync with PermissionUtil.
+            if (caller != Process.SYSTEM_UID
+                    && caller != Process.NETWORK_STACK_UID
+                    && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)) {
+                throw new SecurityException(
+                        "Only the system server should try to bind to the network stack.");
+            }
+        }
+
+        @Override
+        public ConnectivityModuleConnector getConnectivityModuleConnector() {
+            return ConnectivityModuleConnector.getInstance();
+        }
+    }
 
     /**
      * Get the NetworkStackClient singleton instance.
@@ -150,9 +197,7 @@
 
     private void registerNetworkStackService(@NonNull IBinder service) {
         final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service);
-
-        ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
-                DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        mDependencies.addToServiceManager(service);
         log("Network stack service registered");
 
         final ArrayList<NetworkStackCallback> requests;
@@ -185,7 +230,7 @@
      * started.
      */
     public void start() {
-        ConnectivityModuleConnector.getInstance().startModuleService(
+        mDependencies.getConnectivityModuleConnector().startModuleService(
                 INetworkStackConnector.class.getName(), PERMISSION_MAINLINE_NETWORK_STACK,
                 new NetworkStackConnection());
         log("Network stack service start requested");
@@ -251,16 +296,7 @@
     }
 
     private void requestConnector(@NonNull NetworkStackCallback request) {
-        // TODO: PID check.
-        final int caller = Binder.getCallingUid();
-        if (caller != Process.SYSTEM_UID
-                && caller != Process.NETWORK_STACK_UID
-                && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)
-                && !UserHandle.isSameApp(caller, Process.PHONE_UID)) {
-            // Don't even attempt to obtain the connector and give a nice error message
-            throw new SecurityException(
-                    "Only the system server should try to bind to the network stack.");
-        }
+        mDependencies.checkCallerUid();
 
         if (!mWasSystemServerInitialized) {
             // The network stack is not being started in this process, e.g. this process is not
diff --git a/services/net/java/android/net/PrivateDnsConfigParcel.aidl b/services/net/java/android/net/PrivateDnsConfigParcel.aidl
deleted file mode 100644
index b52fce6..0000000
--- a/services/net/java/android/net/PrivateDnsConfigParcel.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-parcelable PrivateDnsConfigParcel {
-    String hostname;
-    String[] ips;
-}
diff --git a/services/net/java/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/java/android/net/ProvisioningConfigurationParcelable.aidl
deleted file mode 100644
index 99606fb..0000000
--- a/services/net/java/android/net/ProvisioningConfigurationParcelable.aidl
+++ /dev/null
@@ -1,38 +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 android.net;
-
-import android.net.InitialConfigurationParcelable;
-import android.net.Network;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-
-parcelable ProvisioningConfigurationParcelable {
-    boolean enableIPv4;
-    boolean enableIPv6;
-    boolean usingMultinetworkPolicyTracker;
-    boolean usingIpReachabilityMonitor;
-    int requestedPreDhcpActionMs;
-    InitialConfigurationParcelable initialConfig;
-    StaticIpConfiguration staticIpConfig;
-    ApfCapabilities apfCapabilities;
-    int provisioningTimeoutMs;
-    int ipv6AddrGenMode;
-    Network network;
-    String displayName;
-}
diff --git a/services/net/java/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/java/android/net/TcpKeepalivePacketDataParcelable.aidl
deleted file mode 100644
index e25168d..0000000
--- a/services/net/java/android/net/TcpKeepalivePacketDataParcelable.aidl
+++ /dev/null
@@ -1,30 +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 android.net;
-
-parcelable TcpKeepalivePacketDataParcelable {
-    byte[] srcAddress;
-    int srcPort;
-    byte[] dstAddress;
-    int dstPort;
-    int seq;
-    int ack;
-    int rcvWnd;
-    int rcvWndScale;
-    int tos;
-    int ttl;
-}
diff --git a/services/net/java/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/java/android/net/dhcp/DhcpServingParamsParcel.aidl
deleted file mode 100644
index 7b8b9ee..0000000
--- a/services/net/java/android/net/dhcp/DhcpServingParamsParcel.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- *
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-parcelable DhcpServingParamsParcel {
-    int serverAddr;
-    int serverAddrPrefixLength;
-    int[] defaultRouters;
-    int[] dnsServers;
-    int[] excludedAddrs;
-    long dhcpLeaseTimeSecs;
-    int linkMtu;
-    boolean metered;
-}
-
diff --git a/services/net/java/android/net/dhcp/IDhcpServer.aidl b/services/net/java/android/net/dhcp/IDhcpServer.aidl
deleted file mode 100644
index 559433b..0000000
--- a/services/net/java/android/net/dhcp/IDhcpServer.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing perNmissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import android.net.INetworkStackStatusCallback;
-import android.net.dhcp.DhcpServingParamsParcel;
-
-/** @hide */
-oneway interface IDhcpServer {
-    const int STATUS_UNKNOWN = 0;
-    const int STATUS_SUCCESS = 1;
-    const int STATUS_INVALID_ARGUMENT = 2;
-    const int STATUS_UNKNOWN_ERROR = 3;
-
-    void start(in INetworkStackStatusCallback cb);
-    void updateParams(in DhcpServingParamsParcel params, in INetworkStackStatusCallback cb);
-    void stop(in INetworkStackStatusCallback cb);
-}
diff --git a/services/net/java/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/java/android/net/dhcp/IDhcpServerCallbacks.aidl
deleted file mode 100644
index 7ab4dcd..0000000
--- a/services/net/java/android/net/dhcp/IDhcpServerCallbacks.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright (c) 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing perNmissions and
- * limitations under the License.
- */
-
-package android.net.dhcp;
-
-import android.net.dhcp.IDhcpServer;
-
-/** @hide */
-oneway interface IDhcpServerCallbacks {
-    void onDhcpServerCreated(int statusCode, in IDhcpServer server);
-}
diff --git a/services/net/java/android/net/ip/IIpClient.aidl b/services/net/java/android/net/ip/IIpClient.aidl
deleted file mode 100644
index 9989c52..0000000
--- a/services/net/java/android/net/ip/IIpClient.aidl
+++ /dev/null
@@ -1,38 +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 perNmissions and
- * limitations under the License.
- */
-package android.net.ip;
-
-import android.net.ProxyInfo;
-import android.net.ProvisioningConfigurationParcelable;
-import android.net.NattKeepalivePacketDataParcelable;
-import android.net.TcpKeepalivePacketDataParcelable;
-
-/** @hide */
-oneway interface IIpClient {
-    void completedPreDhcpAction();
-    void confirmConfiguration();
-    void readPacketFilterComplete(in byte[] data);
-    void shutdown();
-    void startProvisioning(in ProvisioningConfigurationParcelable req);
-    void stop();
-    void setTcpBufferSizes(in String tcpBufferSizes);
-    void setHttpProxy(in ProxyInfo proxyInfo);
-    void setMulticastFilter(boolean enabled);
-    void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt);
-    void removeKeepalivePacketFilter(int slot);
-    void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
-    void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt);
-}
diff --git a/services/net/java/android/net/ip/IIpClientCallbacks.aidl b/services/net/java/android/net/ip/IIpClientCallbacks.aidl
deleted file mode 100644
index 3681416..0000000
--- a/services/net/java/android/net/ip/IIpClientCallbacks.aidl
+++ /dev/null
@@ -1,66 +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 perNmissions and
- * limitations under the License.
- */
-package android.net.ip;
-
-import android.net.LinkProperties;
-import android.net.ip.IIpClient;
-import android.net.DhcpResultsParcelable;
-
-/** @hide */
-oneway interface IIpClientCallbacks {
-    void onIpClientCreated(in IIpClient ipClient);
-
-    void onPreDhcpAction();
-    void onPostDhcpAction();
-
-    // This is purely advisory and not an indication of provisioning
-    // success or failure.  This is only here for callers that want to
-    // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
-    // DHCPv4 or static IPv4 configuration failure or success can be
-    // determined by whether or not the passed-in DhcpResults object is
-    // null or not.
-    void onNewDhcpResults(in DhcpResultsParcelable dhcpResults);
-
-    void onProvisioningSuccess(in LinkProperties newLp);
-    void onProvisioningFailure(in LinkProperties newLp);
-
-    // Invoked on LinkProperties changes.
-    void onLinkPropertiesChange(in LinkProperties newLp);
-
-    // Called when the internal IpReachabilityMonitor (if enabled) has
-    // detected the loss of a critical number of required neighbors.
-    void onReachabilityLost(in String logMsg);
-
-    // Called when the IpClient state machine terminates.
-    void onQuit();
-
-    // Install an APF program to filter incoming packets.
-    void installPacketFilter(in byte[] filter);
-
-    // Asynchronously read back the APF program & data buffer from the wifi driver.
-    // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
-    // buffer. In response to this request, the driver returns the data buffer asynchronously
-    // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
-    void startReadPacketFilter();
-
-    // If multicast filtering cannot be accomplished with APF, this function will be called to
-    // actuate multicast filtering using another means.
-    void setFallbackMulticastFilter(boolean enabled);
-
-    // Enabled/disable Neighbor Discover offload functionality. This is
-    // called, for example, whenever 464xlat is being started or stopped.
-    void setNeighborDiscoveryOffload(boolean enable);
-}
\ No newline at end of file
diff --git a/services/net/java/android/net/ipmemorystore/Blob.aidl b/services/net/java/android/net/ipmemorystore/Blob.aidl
deleted file mode 100644
index 9dbef11..0000000
--- a/services/net/java/android/net/ipmemorystore/Blob.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-/**
- * A blob of data opaque to the memory store. The client mutates this at its own risk,
- * and it is strongly suggested to never do it at all and treat this as immutable.
- * {@hide}
- */
-parcelable Blob {
-    byte[] data;
-}
diff --git a/services/net/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
deleted file mode 100644
index 4926feb..0000000
--- a/services/net/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.StatusParcelable;
-
-/** {@hide} */
-oneway interface IOnBlobRetrievedListener {
-    /**
-     * Private data was retrieved for the L2 key and name specified.
-     * Note this does not return the client ID, as clients are expected to only ever use one ID.
-     */
-     void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
-             in Blob data);
-}
diff --git a/services/net/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
deleted file mode 100644
index dea0cc4..0000000
--- a/services/net/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.net.ipmemorystore.StatusParcelable;
-
-/** {@hide} */
-oneway interface IOnL2KeyResponseListener {
-    /**
-     * The operation completed with the specified L2 key.
-     */
-     void onL2KeyResponse(in StatusParcelable status, in String l2Key);
-}
diff --git a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
deleted file mode 100644
index 870e217..0000000
--- a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.net.ipmemorystore.NetworkAttributesParcelable;
-import android.net.ipmemorystore.StatusParcelable;
-
-/** {@hide} */
-oneway interface IOnNetworkAttributesRetrievedListener {
-    /**
-     * Network attributes were fetched for the specified L2 key. While the L2 key will never
-     * be null, the attributes may be if no data is stored about this L2 key.
-     */
-     void onNetworkAttributesRetrieved(in StatusParcelable status, in String l2Key,
-             in NetworkAttributesParcelable attributes);
-}
diff --git a/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
deleted file mode 100644
index b8ccfb9..0000000
--- a/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
-import android.net.ipmemorystore.StatusParcelable;
-
-/** {@hide} */
-oneway interface IOnSameL3NetworkResponseListener {
-    /**
-     * The memory store has come up with the answer to a query that was sent.
-     */
-     void onSameL3NetworkResponse(in StatusParcelable status,
-             in SameL3NetworkResponseParcelable response);
-}
diff --git a/services/net/java/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/java/android/net/ipmemorystore/IOnStatusListener.aidl
deleted file mode 100644
index 5d07504..0000000
--- a/services/net/java/android/net/ipmemorystore/IOnStatusListener.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.net.ipmemorystore.StatusParcelable;
-
-/** {@hide} */
-oneway interface IOnStatusListener {
-    /**
-     * The operation has completed with the specified status.
-     */
-     void onComplete(in StatusParcelable status);
-}
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java b/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
deleted file mode 100644
index 818515a..0000000
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributes.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringJoiner;
-
-/**
- * A POD object to represent attributes of a single L2 network entry.
- * @hide
- */
-public class NetworkAttributes {
-    private static final boolean DBG = true;
-
-    // Weight cutoff for grouping. To group, a similarity score is computed with the following
-    // algorithm : if both fields are non-null and equals() then add their assigned weight, else if
-    // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT),
-    // otherwise add nothing.
-    // As a guideline, this should be something like 60~75% of the total weights in this class. The
-    // design states "in essence a reader should imagine that if two important columns don't match,
-    // or one important and several unimportant columns don't match then the two records are
-    // considered a different group".
-    private static final float TOTAL_WEIGHT_CUTOFF = 520.0f;
-    // The portion of the weight that is earned when scoring group-sameness by having both columns
-    // being null. This is because some networks rightfully don't have some attributes (e.g. a
-    // V6-only network won't have an assigned V4 address) and both being null should count for
-    // something, but attributes may also be null just because data is unavailable.
-    private static final float NULL_MATCH_WEIGHT = 0.25f;
-
-    // The v4 address that was assigned to this device the last time it joined this network.
-    // This typically comes from DHCP but could be something else like static configuration.
-    // This does not apply to IPv6.
-    // TODO : add a list of v6 prefixes for the v6 case.
-    @Nullable
-    public final Inet4Address assignedV4Address;
-    private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f;
-
-    // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds.
-    @Nullable
-    public final Long assignedV4AddressExpiry;
-    // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the
-    // same L3 network".
-    private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f;
-
-    // Optionally supplied by the client if it has an opinion on L3 network. For example, this
-    // could be a hash of the SSID + security type on WiFi.
-    @Nullable
-    public final String groupHint;
-    private static final float WEIGHT_GROUPHINT = 300.0f;
-
-    // The list of DNS server addresses.
-    @Nullable
-    public final List<InetAddress> dnsAddresses;
-    private static final float WEIGHT_DNSADDRESSES = 200.0f;
-
-    // The mtu on this network.
-    @Nullable
-    public final Integer mtu;
-    private static final float WEIGHT_MTU = 50.0f;
-
-    // The sum of all weights in this class. Tests ensure that this stays equal to the total of
-    // all weights.
-    /** @hide */
-    @VisibleForTesting
-    public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR
-            + WEIGHT_ASSIGNEDV4ADDREXPIRY
-            + WEIGHT_GROUPHINT
-            + WEIGHT_DNSADDRESSES
-            + WEIGHT_MTU;
-
-    /** @hide */
-    @VisibleForTesting
-    public NetworkAttributes(
-            @Nullable final Inet4Address assignedV4Address,
-            @Nullable final Long assignedV4AddressExpiry,
-            @Nullable final String groupHint,
-            @Nullable final List<InetAddress> dnsAddresses,
-            @Nullable final Integer mtu) {
-        if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
-        if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) {
-            throw new IllegalArgumentException("lease expiry can't be negative or zero");
-        }
-        this.assignedV4Address = assignedV4Address;
-        this.assignedV4AddressExpiry = assignedV4AddressExpiry;
-        this.groupHint = groupHint;
-        this.dnsAddresses = null == dnsAddresses ? null :
-                Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
-        this.mtu = mtu;
-    }
-
-    @VisibleForTesting
-    public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
-        // The call to the other constructor must be the first statement of this constructor,
-        // so everything has to be inline
-        this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
-                parcelable.assignedV4AddressExpiry > 0
-                        ? parcelable.assignedV4AddressExpiry : null,
-                parcelable.groupHint,
-                blobArrayToInetAddressList(parcelable.dnsAddresses),
-                parcelable.mtu >= 0 ? parcelable.mtu : null);
-    }
-
-    @Nullable
-    private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
-        if (null == address) return null;
-        try {
-            return InetAddress.getByAddress(address);
-        } catch (UnknownHostException e) {
-            return null;
-        }
-    }
-
-    @Nullable
-    private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
-        if (null == blobs) return null;
-        final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
-        for (final Blob b : blobs) {
-            final InetAddress addr = getByAddressOrNull(b.data);
-            if (null != addr) list.add(addr);
-        }
-        return list;
-    }
-
-    @Nullable
-    private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
-        if (null == addresses) return null;
-        final ArrayList<Blob> blobs = new ArrayList<>();
-        for (int i = 0; i < addresses.size(); ++i) {
-            final InetAddress addr = addresses.get(i);
-            if (null == addr) continue;
-            final Blob b = new Blob();
-            b.data = addr.getAddress();
-            blobs.add(b);
-        }
-        return blobs.toArray(new Blob[0]);
-    }
-
-    /** Converts this NetworkAttributes to a parcelable object */
-    @NonNull
-    public NetworkAttributesParcelable toParcelable() {
-        final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
-        parcelable.assignedV4Address =
-                (null == assignedV4Address) ? null : assignedV4Address.getAddress();
-        parcelable.assignedV4AddressExpiry =
-                (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry;
-        parcelable.groupHint = groupHint;
-        parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
-        parcelable.mtu = (null == mtu) ? -1 : mtu;
-        return parcelable;
-    }
-
-    private float samenessContribution(final float weight,
-            @Nullable final Object o1, @Nullable final Object o2) {
-        if (null == o1) {
-            return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f;
-        }
-        return Objects.equals(o1, o2) ? weight : 0f;
-    }
-
-    /** @hide */
-    public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) {
-        final float samenessScore =
-                samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address)
-                + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry,
-                      o.assignedV4AddressExpiry)
-                + samenessContribution(WEIGHT_GROUPHINT, groupHint, o.groupHint)
-                + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses)
-                + samenessContribution(WEIGHT_MTU, mtu, o.mtu);
-        // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and
-        // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that
-        // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be).
-        // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff
-        // between 0.5 and 1.0.
-        if (samenessScore < TOTAL_WEIGHT_CUTOFF) {
-            return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2);
-        } else {
-            return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2
-                    + 0.5f;
-        }
-    }
-
-    /** @hide */
-    public static class Builder {
-        @Nullable
-        private Inet4Address mAssignedAddress;
-        @Nullable
-        private Long mAssignedAddressExpiry;
-        @Nullable
-        private String mGroupHint;
-        @Nullable
-        private List<InetAddress> mDnsAddresses;
-        @Nullable
-        private Integer mMtu;
-
-        /**
-         * Set the assigned address.
-         * @param assignedV4Address The assigned address.
-         * @return This builder.
-         */
-        public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
-            mAssignedAddress = assignedV4Address;
-            return this;
-        }
-
-        /**
-         * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used
-         * to represent "infinite lease".
-         *
-         * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address.
-         * @return This builder.
-         */
-        public Builder setAssignedV4AddressExpiry(
-                @Nullable final Long assignedV4AddressExpiry) {
-            if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) {
-                throw new IllegalArgumentException("lease expiry can't be negative or zero");
-            }
-            mAssignedAddressExpiry = assignedV4AddressExpiry;
-            return this;
-        }
-
-        /**
-         * Set the group hint.
-         * @param groupHint The group hint.
-         * @return This builder.
-         */
-        public Builder setGroupHint(@Nullable final String groupHint) {
-            mGroupHint = groupHint;
-            return this;
-        }
-
-        /**
-         * Set the DNS addresses.
-         * @param dnsAddresses The DNS addresses.
-         * @return This builder.
-         */
-        public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
-            if (DBG && null != dnsAddresses) {
-                // Parceling code crashes if one of the addresses is null, therefore validate
-                // them when running in debug.
-                for (final InetAddress address : dnsAddresses) {
-                    if (null == address) throw new IllegalArgumentException("Null DNS address");
-                }
-            }
-            this.mDnsAddresses = dnsAddresses;
-            return this;
-        }
-
-        /**
-         * Set the MTU.
-         * @param mtu The MTU.
-         * @return This builder.
-         */
-        public Builder setMtu(@Nullable final Integer mtu) {
-            if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
-            mMtu = mtu;
-            return this;
-        }
-
-        /**
-         * Return the built NetworkAttributes object.
-         * @return The built NetworkAttributes object.
-         */
-        public NetworkAttributes build() {
-            return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry,
-                  mGroupHint, mDnsAddresses, mMtu);
-        }
-    }
-
-    /** @hide */
-    public boolean isEmpty() {
-        return (null == assignedV4Address) && (null == assignedV4AddressExpiry)
-                && (null == groupHint) && (null == dnsAddresses) && (null == mtu);
-    }
-
-    @Override
-    public boolean equals(@Nullable final Object o) {
-        if (!(o instanceof NetworkAttributes)) return false;
-        final NetworkAttributes other = (NetworkAttributes) o;
-        return Objects.equals(assignedV4Address, other.assignedV4Address)
-                && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry)
-                && Objects.equals(groupHint, other.groupHint)
-                && Objects.equals(dnsAddresses, other.dnsAddresses)
-                && Objects.equals(mtu, other.mtu);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(assignedV4Address, assignedV4AddressExpiry,
-                groupHint, dnsAddresses, mtu);
-    }
-
-    /** Pretty print */
-    @Override
-    public String toString() {
-        final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
-        final ArrayList<String> nullFields = new ArrayList<>();
-
-        if (null != assignedV4Address) {
-            resultJoiner.add("assignedV4Addr :");
-            resultJoiner.add(assignedV4Address.toString());
-        } else {
-            nullFields.add("assignedV4Addr");
-        }
-
-        if (null != assignedV4AddressExpiry) {
-            resultJoiner.add("assignedV4AddressExpiry :");
-            resultJoiner.add(assignedV4AddressExpiry.toString());
-        } else {
-            nullFields.add("assignedV4AddressExpiry");
-        }
-
-        if (null != groupHint) {
-            resultJoiner.add("groupHint :");
-            resultJoiner.add(groupHint);
-        } else {
-            nullFields.add("groupHint");
-        }
-
-        if (null != dnsAddresses) {
-            resultJoiner.add("dnsAddr : [");
-            for (final InetAddress addr : dnsAddresses) {
-                resultJoiner.add(addr.getHostAddress());
-            }
-            resultJoiner.add("]");
-        } else {
-            nullFields.add("dnsAddr");
-        }
-
-        if (null != mtu) {
-            resultJoiner.add("mtu :");
-            resultJoiner.add(mtu.toString());
-        } else {
-            nullFields.add("mtu");
-        }
-
-        if (!nullFields.isEmpty()) {
-            resultJoiner.add("; Null fields : [");
-            for (final String field : nullFields) {
-                resultJoiner.add(field);
-            }
-            resultJoiner.add("]");
-        }
-
-        return resultJoiner.toString();
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
deleted file mode 100644
index 997eb2b..0000000
--- a/services/net/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
-// of arrays.
-import android.net.ipmemorystore.Blob;
-
-/**
- * An object to represent attributes of a single L2 network entry.
- * See NetworkAttributes.java for a description of each field. The types used in this class
- * are structured parcelable types instead of the richer types of the NetworkAttributes object,
- * but they have the same purpose. The NetworkAttributes.java file also contains the code
- * to convert the richer types to the parcelable types and back.
- * @hide
- */
-parcelable NetworkAttributesParcelable {
-    byte[] assignedV4Address;
-    long assignedV4AddressExpiry;
-    String groupHint;
-    Blob[] dnsAddresses;
-    int mtu;
-}
diff --git a/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java
deleted file mode 100644
index a17483a..0000000
--- a/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java
+++ /dev/null
@@ -1,50 +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 android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-
-/**
- * A listener for the IpMemoryStore to return a blob.
- * @hide
- */
-public interface OnBlobRetrievedListener {
-    /**
-     * The memory store has come up with the answer to a query that was sent.
-     */
-    void onBlobRetrieved(Status status, String l2Key, String name, Blob blob);
-
-    /** Converts this OnBlobRetrievedListener to a parcelable object */
-    @NonNull
-    static IOnBlobRetrievedListener toAIDL(@NonNull final OnBlobRetrievedListener listener) {
-        return new IOnBlobRetrievedListener.Stub() {
-            @Override
-            public void onBlobRetrieved(final StatusParcelable statusParcelable, final String l2Key,
-                    final String name, final Blob blob) {
-                // NonNull, but still don't crash the system server if null
-                if (null != listener) {
-                    listener.onBlobRetrieved(new Status(statusParcelable), l2Key, name, blob);
-                }
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java b/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java
deleted file mode 100644
index e608aec..0000000
--- a/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java
+++ /dev/null
@@ -1,50 +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 android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-
-/**
- * A listener for the IpMemoryStore to return a L2 key.
- * @hide
- */
-public interface OnL2KeyResponseListener {
-    /**
-     * The operation has completed with the specified status.
-     */
-    void onL2KeyResponse(Status status, String l2Key);
-
-    /** Converts this OnL2KeyResponseListener to a parcelable object */
-    @NonNull
-    static IOnL2KeyResponseListener toAIDL(@NonNull final OnL2KeyResponseListener listener) {
-        return new IOnL2KeyResponseListener.Stub() {
-            @Override
-            public void onL2KeyResponse(final StatusParcelable statusParcelable,
-                    final String l2Key) {
-                // NonNull, but still don't crash the system server if null
-                if (null != listener) {
-                    listener.onL2KeyResponse(new Status(statusParcelable), l2Key);
-                }
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
deleted file mode 100644
index 395ad98..0000000
--- a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
+++ /dev/null
@@ -1,54 +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 android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-
-/**
- * A listener for the IpMemoryStore to return network attributes.
- * @hide
- */
-public interface OnNetworkAttributesRetrievedListener {
-    /**
-     * The memory store has come up with the answer to a query that was sent.
-     */
-    void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attributes);
-
-    /** Converts this OnNetworkAttributesRetrievedListener to a parcelable object */
-    @NonNull
-    static IOnNetworkAttributesRetrievedListener toAIDL(
-            @NonNull final OnNetworkAttributesRetrievedListener listener) {
-        return new IOnNetworkAttributesRetrievedListener.Stub() {
-            @Override
-            public void onNetworkAttributesRetrieved(final StatusParcelable statusParcelable,
-                    final String l2Key,
-                    final NetworkAttributesParcelable networkAttributesParcelable) {
-                // NonNull, but still don't crash the system server if null
-                if (null != listener) {
-                    listener.onNetworkAttributesRetrieved(
-                            new Status(statusParcelable), l2Key, null == networkAttributesParcelable
-                                ? null : new NetworkAttributes(networkAttributesParcelable));
-                }
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java b/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
deleted file mode 100644
index 67f8da8..0000000
--- a/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
+++ /dev/null
@@ -1,53 +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 android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-
-/**
- * A listener for the IpMemoryStore to return a response about network sameness.
- * @hide
- */
-public interface OnSameL3NetworkResponseListener {
-    /**
-     * The memory store has come up with the answer to a query that was sent.
-     */
-    void onSameL3NetworkResponse(Status status, SameL3NetworkResponse response);
-
-    /** Converts this OnSameL3NetworkResponseListener to a parcelable object */
-    @NonNull
-    static IOnSameL3NetworkResponseListener toAIDL(
-            @NonNull final OnSameL3NetworkResponseListener listener) {
-        return new IOnSameL3NetworkResponseListener.Stub() {
-            @Override
-            public void onSameL3NetworkResponse(final StatusParcelable statusParcelable,
-                    final SameL3NetworkResponseParcelable sameL3NetworkResponseParcelable) {
-                // NonNull, but still don't crash the system server if null
-                if (null != listener) {
-                    listener.onSameL3NetworkResponse(
-                            new Status(statusParcelable),
-                            new SameL3NetworkResponse(sameL3NetworkResponseParcelable));
-                }
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/OnStatusListener.java b/services/net/java/android/net/ipmemorystore/OnStatusListener.java
deleted file mode 100644
index 4262efd..0000000
--- a/services/net/java/android/net/ipmemorystore/OnStatusListener.java
+++ /dev/null
@@ -1,49 +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 android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-/**
- * A listener for the IpMemoryStore to return a status to a client.
- * @hide
- */
-public interface OnStatusListener {
-    /**
-     * The operation has completed with the specified status.
-     */
-    void onComplete(Status status);
-
-    /** Converts this OnStatusListener to a parcelable object */
-    @NonNull
-    static IOnStatusListener toAIDL(@Nullable final OnStatusListener listener) {
-        return new IOnStatusListener.Stub() {
-            @Override
-            public void onComplete(final StatusParcelable statusParcelable) {
-                if (null != listener) {
-                    listener.onComplete(new Status(statusParcelable));
-                }
-            }
-
-            @Override
-            public int getInterfaceVersion() {
-                return this.VERSION;
-            }
-        };
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/services/net/java/android/net/ipmemorystore/SameL3NetworkResponse.java
deleted file mode 100644
index 291aca8..0000000
--- a/services/net/java/android/net/ipmemorystore/SameL3NetworkResponse.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * An object representing the answer to a query whether two given L2 networks represent the
- * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
- * @hide
- */
-public class SameL3NetworkResponse {
-    @IntDef(prefix = "NETWORK_",
-            value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface NetworkSameness {}
-
-    /**
-     * Both L2 networks represent the same L3 network.
-     */
-    public static final int NETWORK_SAME = 1;
-
-    /**
-     * The two L2 networks represent a different L3 network.
-     */
-    public static final int NETWORK_DIFFERENT = 2;
-
-    /**
-     * The device has never connected to at least one of these two L2 networks, or data
-     * has been wiped. Therefore the device has never seen the L3 network behind at least
-     * one of these two L2 networks, and can't evaluate whether it's the same as the other.
-     */
-    public static final int NETWORK_NEVER_CONNECTED = 3;
-
-    /**
-     * The first L2 key specified in the query.
-     */
-    @NonNull
-    public final String l2Key1;
-
-    /**
-     * The second L2 key specified in the query.
-     */
-    @NonNull
-    public final String l2Key2;
-
-    /**
-     * A confidence value indicating whether the two L2 networks represent the same L3 network.
-     *
-     * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
-     * representing complete confidence that the given L2 networks represent a different
-     * L3 network, and 1.0 representing complete confidence that the given L2 networks
-     * represent the same L3 network.
-     * If at least one of the L2 networks was not known, this value will be outside of the
-     * 0.0~1.0 range.
-     *
-     * Most apps should not be interested in this, and are encouraged to use the collapsing
-     * {@link #getNetworkSameness()} function below.
-     */
-    public final float confidence;
-
-    /**
-     * @return whether the two L2 networks represent the same L3 network. Either
-     *     {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
-     */
-    @NetworkSameness
-    public final int getNetworkSameness() {
-        if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
-        return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
-    }
-
-    /** @hide */
-    public SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
-            final float confidence) {
-        this.l2Key1 = l2Key1;
-        this.l2Key2 = l2Key2;
-        this.confidence = confidence;
-    }
-
-    /** Builds a SameL3NetworkResponse from a parcelable object */
-    @VisibleForTesting
-    public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
-        this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
-    }
-
-    /** Converts this SameL3NetworkResponse to a parcelable object */
-    @NonNull
-    public SameL3NetworkResponseParcelable toParcelable() {
-        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
-        parcelable.l2Key1 = l2Key1;
-        parcelable.l2Key2 = l2Key2;
-        parcelable.confidence = confidence;
-        return parcelable;
-    }
-
-    // Note key1 and key2 have to match each other for this to return true. If
-    // key1 matches o.key2 and the other way around this returns false.
-    @Override
-    public boolean equals(@Nullable final Object o) {
-        if (!(o instanceof SameL3NetworkResponse)) return false;
-        final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
-        return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
-                && confidence == other.confidence;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(l2Key1, l2Key2, confidence);
-    }
-
-    @Override
-    /** Pretty print */
-    public String toString() {
-        switch (getNetworkSameness()) {
-            case NETWORK_SAME:
-                return "\"" + l2Key1 + "\" same L3 network as \"" + l2Key2 + "\"";
-            case NETWORK_DIFFERENT:
-                return "\"" + l2Key1 + "\" different L3 network from \"" + l2Key2 + "\"";
-            case NETWORK_NEVER_CONNECTED:
-                return "\"" + l2Key1 + "\" can't be tested against \"" + l2Key2 + "\"";
-            default:
-                return "Buggy sameness value ? \"" + l2Key1 + "\", \"" + l2Key2 + "\"";
-        }
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
deleted file mode 100644
index 7196699..0000000
--- a/services/net/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-/** {@hide} */
-parcelable SameL3NetworkResponseParcelable {
-    String l2Key1;
-    String l2Key2;
-    float confidence;
-}
diff --git a/services/net/java/android/net/ipmemorystore/Status.java b/services/net/java/android/net/ipmemorystore/Status.java
deleted file mode 100644
index 13242c0..0000000
--- a/services/net/java/android/net/ipmemorystore/Status.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-import android.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-/**
- * A parcelable status representing the result of an operation.
- * Parcels as StatusParceled.
- * @hide
- */
-public class Status {
-    public static final int SUCCESS = 0;
-
-    public static final int ERROR_GENERIC = -1;
-    public static final int ERROR_ILLEGAL_ARGUMENT = -2;
-    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
-    public static final int ERROR_STORAGE = -4;
-    public static final int ERROR_UNKNOWN = -5;
-
-    public final int resultCode;
-
-    public Status(final int resultCode) {
-        this.resultCode = resultCode;
-    }
-
-    @VisibleForTesting
-    public Status(@NonNull final StatusParcelable parcelable) {
-        this(parcelable.resultCode);
-    }
-
-    /** Converts this Status to a parcelable object */
-    @NonNull
-    public StatusParcelable toParcelable() {
-        final StatusParcelable parcelable = new StatusParcelable();
-        parcelable.resultCode = resultCode;
-        return parcelable;
-    }
-
-    public boolean isSuccess() {
-        return SUCCESS == resultCode;
-    }
-
-    /** Pretty print */
-    @Override
-    public String toString() {
-        switch (resultCode) {
-            case SUCCESS: return "SUCCESS";
-            case ERROR_GENERIC: return "GENERIC ERROR";
-            case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
-            case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
-            // "DB storage error" is not very helpful but SQLite does not provide specific error
-            // codes upon store failure. Thus this indicates SQLite returned some error upon store
-            case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
-            default: return "Unknown value ?!";
-        }
-    }
-}
diff --git a/services/net/java/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/java/android/net/ipmemorystore/StatusParcelable.aidl
deleted file mode 100644
index fb36ef4..0000000
--- a/services/net/java/android/net/ipmemorystore/StatusParcelable.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.ipmemorystore;
-
-/** {@hide} */
-parcelable StatusParcelable {
-  int resultCode;
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
deleted file mode 100644
index 3f57240..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkHashTest {
-    private static final int HASH_LENGTH_BYTES = 256 / 8;
-    private static final byte[] TEST_HASH_1 = Arrays.copyOf(new byte[] {1}, HASH_LENGTH_BYTES);
-    private static final byte[] TEST_HASH_2 = Arrays.copyOf(new byte[] {2}, HASH_LENGTH_BYTES);
-
-    @Test
-    public void testGetHash_returnsHash() {
-        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);
-
-        byte[] hash = chunkHash.getHash();
-
-        assertThat(hash).asList().containsExactlyElementsIn(Bytes.asList(TEST_HASH_1)).inOrder();
-    }
-
-    @Test
-    public void testEquals() {
-        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
-
-        assertThat(chunkHash1).isEqualTo(equalChunkHash1);
-        assertThat(chunkHash1).isNotEqualTo(chunkHash2);
-    }
-
-    @Test
-    public void testHashCode() {
-        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
-
-        int hash1 = chunkHash1.hashCode();
-        int equalHash1 = equalChunkHash1.hashCode();
-        int hash2 = chunkHash2.hashCode();
-
-        assertThat(hash1).isEqualTo(equalHash1);
-        assertThat(hash1).isNotEqualTo(hash2);
-    }
-
-    @Test
-    public void testCompareTo_whenEqual_returnsZero() {
-        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);
-        ChunkHash equalChunkHash = new ChunkHash(TEST_HASH_1);
-
-        int result = chunkHash.compareTo(equalChunkHash);
-
-        assertThat(result).isEqualTo(0);
-    }
-
-    @Test
-    public void testCompareTo_whenArgumentGreater_returnsNegative() {
-        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
-
-        int result = chunkHash1.compareTo(chunkHash2);
-
-        assertThat(result).isLessThan(0);
-    }
-
-    @Test
-    public void testCompareTo_whenArgumentSmaller_returnsPositive() {
-        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
-        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);
-
-        int result = chunkHash2.compareTo(chunkHash1);
-
-        assertThat(result).isGreaterThan(0);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
deleted file mode 100644
index 24e5573..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.Preconditions;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkListingMapTest {
-    private static final String CHUNK_A = "CHUNK_A";
-    private static final String CHUNK_B = "CHUNK_B";
-    private static final String CHUNK_C = "CHUNK_C";
-
-    private static final int CHUNK_A_LENGTH = 256;
-    private static final int CHUNK_B_LENGTH = 1024;
-    private static final int CHUNK_C_LENGTH = 4055;
-
-    private ChunkHash mChunkHashA;
-    private ChunkHash mChunkHashB;
-    private ChunkHash mChunkHashC;
-
-    @Before
-    public void setUp() throws Exception {
-        mChunkHashA = getHash(CHUNK_A);
-        mChunkHashB = getHash(CHUNK_B);
-        mChunkHashC = getHash(CHUNK_C);
-    }
-
-    @Test
-    public void testHasChunk_whenChunkInListing_returnsTrue() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        boolean chunkAInList = chunkListingMap.hasChunk(mChunkHashA);
-        boolean chunkBInList = chunkListingMap.hasChunk(mChunkHashB);
-        boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
-
-        assertThat(chunkAInList).isTrue();
-        assertThat(chunkBInList).isTrue();
-        assertThat(chunkCInList).isTrue();
-    }
-
-    @Test
-    public void testHasChunk_whenChunkNotInListing_returnsFalse() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-        ChunkHash chunkHashEmpty = getHash("");
-
-        boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
-        boolean emptyChunkInList = chunkListingMap.hasChunk(chunkHashEmpty);
-
-        assertThat(chunkCInList).isFalse();
-        assertThat(emptyChunkInList).isFalse();
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsEntryWithCorrectLength() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
-        ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
-        ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(entryA.getLength()).isEqualTo(CHUNK_A_LENGTH);
-        assertThat(entryB.getLength()).isEqualTo(CHUNK_B_LENGTH);
-        assertThat(entryC.getLength()).isEqualTo(CHUNK_C_LENGTH);
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsEntryWithCorrectStart() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
-        ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
-        ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(entryA.getStart()).isEqualTo(0);
-        assertThat(entryB.getStart()).isEqualTo(CHUNK_A_LENGTH);
-        assertThat(entryC.getStart()).isEqualTo(CHUNK_A_LENGTH + CHUNK_B_LENGTH);
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsNullForNonExistentChunk() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry chunkEntryNonexistentChunk =
-                chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(chunkEntryNonexistentChunk).isNull();
-    }
-
-    @Test
-    public void testReadFromProto_whenEmptyProto_returnsChunkListingMapWith0Chunks()
-            throws Exception {
-        ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
-        ChunkListingMap chunkListingMap = ChunkListingMap.readFromProto(emptyProto);
-
-        assertThat(chunkListingMap.getChunkCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_returnsChunkListingWithCorrectSize() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        assertThat(chunkListingMap.getChunkCount()).isEqualTo(3);
-    }
-
-    private byte[] createChunkListingProto(ChunkHash[] hashes, int[] lengths) {
-        Preconditions.checkArgument(hashes.length == lengths.length);
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-
-        for (int i = 0; i < hashes.length; ++i) {
-            writeToProtoOutputStream(outputStream, hashes[i], lengths[i]);
-        }
-        outputStream.flush();
-
-        return outputStream.getBytes();
-    }
-
-    private void writeToProtoOutputStream(ProtoOutputStream out, ChunkHash chunkHash, int length) {
-        long token = out.start(ChunksMetadataProto.ChunkListing.CHUNKS);
-        out.write(ChunksMetadataProto.Chunk.HASH, chunkHash.getHash());
-        out.write(ChunksMetadataProto.Chunk.LENGTH, length);
-        out.end(token);
-    }
-
-    private ChunkHash getHash(String name) {
-        return new ChunkHash(
-                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java
deleted file mode 100644
index 17c9a86..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkTest {
-    private static final String CHUNK_A = "CHUNK_A";
-    private static final int CHUNK_A_LENGTH = 256;
-
-    private ChunkHash mChunkHashA;
-
-    @Before
-    public void setUp() throws Exception {
-        mChunkHashA = getHash(CHUNK_A);
-    }
-
-    @Test
-    public void testReadFromProto_readsCorrectly() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    @Test
-    public void testReadFromProto_whenFieldsWrittenInReversedOrder_readsCorrectly()
-            throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        // Write fields of Chunk proto in reverse order.
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    @Test
-    public void testReadFromProto_whenEmptyProto_returnsEmptyHash() throws Exception {
-        ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
-        Chunk chunk = Chunk.readFromProto(emptyProto);
-
-        assertThat(chunk.getHash()).asList().hasSize(0);
-        assertThat(chunk.getLength()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_whenOnlyHashSet_returnsChunkWithOnlyHash() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_whenOnlyLengthSet_returnsChunkWithOnlyLength() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(new byte[] {});
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    private ChunkHash getHash(String name) {
-        return new ChunkHash(
-                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
deleted file mode 100644
index 0bf1417..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class EncryptedChunkOrderingTest {
-    private static final byte[] TEST_BYTE_ARRAY_1 = new byte[] {1, 2, 3, 4, 5};
-    private static final byte[] TEST_BYTE_ARRAY_2 = new byte[] {5, 4, 3, 2, 1};
-
-    @Test
-    public void testEncryptedChunkOrdering_returnsValue() {
-        EncryptedChunkOrdering encryptedChunkOrdering =
-                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
-
-        byte[] bytes = encryptedChunkOrdering.encryptedChunkOrdering();
-
-        assertThat(bytes)
-                .asList()
-                .containsExactlyElementsIn(Bytes.asList(TEST_BYTE_ARRAY_1))
-                .inOrder();
-    }
-
-    @Test
-    public void testEquals() {
-        EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
-        EncryptedChunkOrdering equalChunkOrdering1 =
-                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
-        EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2);
-
-        assertThat(chunkOrdering1).isEqualTo(equalChunkOrdering1);
-        assertThat(chunkOrdering1).isNotEqualTo(chunkOrdering2);
-    }
-
-    @Test
-    public void testHashCode() {
-        EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
-        EncryptedChunkOrdering equalChunkOrdering1 =
-                EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1);
-        EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2);
-
-        int hash1 = chunkOrdering1.hashCode();
-        int equalHash1 = equalChunkOrdering1.hashCode();
-        int hash2 = chunkOrdering2.hashCode();
-
-        assertThat(hash1).isEqualTo(equalHash1);
-        assertThat(hash1).isNotEqualTo(hash2);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
deleted file mode 100644
index d0e5fb3..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.robolectric.RobolectricTestRunner;
-
-import java.security.SecureRandom;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkEncryptorTest {
-    private static final String MAC_ALGORITHM = "HmacSHA256";
-    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
-    private static final int GCM_TAG_LENGTH_BYTES = 16;
-    private static final String CHUNK_PLAINTEXT =
-            "A little Learning is a dang'rous Thing;\n"
-                    + "Drink deep, or taste not the Pierian Spring:\n"
-                    + "There shallow Draughts intoxicate the Brain,\n"
-                    + "And drinking largely sobers us again.";
-    private static final byte[] PLAINTEXT_BYTES = CHUNK_PLAINTEXT.getBytes(UTF_8);
-    private static final byte[] NONCE_1 = "0123456789abc".getBytes(UTF_8);
-    private static final byte[] NONCE_2 = "123456789abcd".getBytes(UTF_8);
-
-    private static final byte[][] NONCES = new byte[][] {NONCE_1, NONCE_2};
-
-    @Mock private SecureRandom mSecureRandomMock;
-    private SecretKey mSecretKey;
-    private ChunkHash mPlaintextHash;
-    private ChunkEncryptor mChunkEncryptor;
-
-    @Before
-    public void setUp() throws Exception {
-        mSecretKey = generateAesKey();
-        ChunkHasher chunkHasher = new ChunkHasher(mSecretKey);
-        mPlaintextHash = chunkHasher.computeHash(PLAINTEXT_BYTES);
-        mSecureRandomMock = mock(SecureRandom.class);
-        mChunkEncryptor = new ChunkEncryptor(mSecretKey, mSecureRandomMock);
-
-        // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes().
-        doAnswer(
-                        new Answer<Void>() {
-                            private int mInvocation = 0;
-
-                            @Override
-                            public Void answer(InvocationOnMock invocation) {
-                                byte[] nonceDestination = invocation.getArgument(0);
-                                System.arraycopy(
-                                        NONCES[this.mInvocation],
-                                        0,
-                                        nonceDestination,
-                                        0,
-                                        GCM_NONCE_LENGTH_BYTES);
-                                this.mInvocation++;
-                                return null;
-                            }
-                        })
-                .when(mSecureRandomMock)
-                .nextBytes(any(byte[].class));
-    }
-
-    @Test
-    public void encrypt_withHash_resultContainsHashAsKey() throws Exception {
-        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        assertThat(chunk.key()).isEqualTo(mPlaintextHash);
-    }
-
-    @Test
-    public void encrypt_generatesHmacOfPlaintext() throws Exception {
-        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        byte[] generatedHash = chunk.key().getHash();
-        Mac mac = Mac.getInstance(MAC_ALGORITHM);
-        mac.init(mSecretKey);
-        byte[] plaintextHmac = mac.doFinal(PLAINTEXT_BYTES);
-        assertThat(generatedHash).isEqualTo(plaintextHmac);
-    }
-
-    @Test
-    public void encrypt_whenInvokedAgain_generatesNewNonce() throws Exception {
-        EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        assertThat(chunk1.nonce()).isNotEqualTo(chunk2.nonce());
-    }
-
-    @Test
-    public void encrypt_whenInvokedAgain_generatesNewCiphertext() throws Exception {
-        EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        assertThat(chunk1.encryptedBytes()).isNotEqualTo(chunk2.encryptedBytes());
-    }
-
-    @Test
-    public void encrypt_generates12ByteNonce() throws Exception {
-        EncryptedChunk encryptedChunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        byte[] nonce = encryptedChunk.nonce();
-        assertThat(nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
-    }
-
-    @Test
-    public void encrypt_decryptedResultCorrespondsToPlaintext() throws Exception {
-        EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
-
-        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
-        cipher.init(
-                Cipher.DECRYPT_MODE,
-                mSecretKey,
-                new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, chunk.nonce()));
-        byte[] decrypted = cipher.doFinal(chunk.encryptedBytes());
-        assertThat(decrypted).isEqualTo(PLAINTEXT_BYTES);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
deleted file mode 100644
index 2bbbf28..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkHasherTest {
-    private static final String KEY_ALGORITHM = "AES";
-    private static final String MAC_ALGORITHM = "HmacSHA256";
-
-    private static final byte[] TEST_KEY = {100, 120};
-    private static final byte[] TEST_DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
-
-    private SecretKey mSecretKey;
-    private ChunkHasher mChunkHasher;
-
-    @Before
-    public void setUp() throws Exception {
-        mSecretKey = new SecretKeySpec(TEST_KEY, KEY_ALGORITHM);
-        mChunkHasher = new ChunkHasher(mSecretKey);
-    }
-
-    @Test
-    public void computeHash_returnsHmacForData() throws Exception {
-        ChunkHash chunkHash = mChunkHasher.computeHash(TEST_DATA);
-
-        byte[] hash = chunkHash.getHash();
-        Mac mac = Mac.getInstance(MAC_ALGORITHM);
-        mac.init(mSecretKey);
-        byte[] expectedHash = mac.doFinal(TEST_DATA);
-        assertThat(hash).isEqualTo(expectedHash);
-    }
-
-    @Test
-    public void computeHash_generates256BitHmac() throws Exception {
-        int expectedLength = 256 / Byte.SIZE;
-
-        byte[] hash = mChunkHasher.computeHash(TEST_DATA).getHash();
-
-        assertThat(hash).hasLength(expectedLength);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
deleted file mode 100644
index 8e801a1..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class EncryptedChunkTest {
-    private static final byte[] CHUNK_HASH_1_BYTES =
-            Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES);
-    private static final byte[] NONCE_1 =
-            Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES);
-    private static final byte[] ENCRYPTED_BYTES_1 =
-            Arrays.copyOf(new byte[] {3}, EncryptedChunk.KEY_LENGTH_BYTES);
-
-    private static final byte[] CHUNK_HASH_2_BYTES =
-            Arrays.copyOf(new byte[] {4}, ChunkHash.HASH_LENGTH_BYTES);
-    private static final byte[] NONCE_2 =
-            Arrays.copyOf(new byte[] {5}, EncryptedChunk.NONCE_LENGTH_BYTES);
-    private static final byte[] ENCRYPTED_BYTES_2 =
-            Arrays.copyOf(new byte[] {6}, EncryptedChunk.KEY_LENGTH_BYTES);
-
-    @Test
-    public void testCreate_withIncorrectLength_throwsException() {
-        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
-        byte[] shortNonce = Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES - 1);
-
-        assertThrows(
-                IllegalArgumentException.class,
-                () -> EncryptedChunk.create(chunkHash, shortNonce, ENCRYPTED_BYTES_1));
-    }
-
-    @Test
-    public void testEncryptedBytes_forNewlyCreatedObject_returnsCorrectValue() {
-        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
-        EncryptedChunk encryptedChunk =
-                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
-
-        byte[] returnedBytes = encryptedChunk.encryptedBytes();
-
-        assertThat(returnedBytes)
-                .asList()
-                .containsExactlyElementsIn(Bytes.asList(ENCRYPTED_BYTES_1))
-                .inOrder();
-    }
-
-    @Test
-    public void testKey_forNewlyCreatedObject_returnsCorrectValue() {
-        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
-        EncryptedChunk encryptedChunk =
-                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
-
-        ChunkHash returnedKey = encryptedChunk.key();
-
-        assertThat(returnedKey).isEqualTo(chunkHash);
-    }
-
-    @Test
-    public void testNonce_forNewlycreatedObject_returnCorrectValue() {
-        ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES);
-        EncryptedChunk encryptedChunk =
-                EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1);
-
-        byte[] returnedNonce = encryptedChunk.nonce();
-
-        assertThat(returnedNonce).asList().containsExactlyElementsIn(Bytes.asList(NONCE_1));
-    }
-
-    @Test
-    public void testEquals() {
-        ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
-        ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
-        ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
-        EncryptedChunk encryptedChunk1 =
-                EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
-        EncryptedChunk equalEncryptedChunk1 =
-                EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
-        EncryptedChunk encryptedChunk2 =
-                EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
-
-        assertThat(encryptedChunk1).isEqualTo(equalEncryptedChunk1);
-        assertThat(encryptedChunk1).isNotEqualTo(encryptedChunk2);
-    }
-
-    @Test
-    public void testHashCode() {
-        ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
-        ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES);
-        ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES);
-        EncryptedChunk encryptedChunk1 =
-                EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
-        EncryptedChunk equalEncryptedChunk1 =
-                EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1);
-        EncryptedChunk encryptedChunk2 =
-                EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2);
-
-        int hash1 = encryptedChunk1.hashCode();
-        int equalHash1 = equalEncryptedChunk1.hashCode();
-        int hash2 = encryptedChunk2.hashCode();
-
-        assertThat(hash1).isEqualTo(equalHash1);
-        assertThat(hash1).isNotEqualTo(hash2);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
deleted file mode 100644
index 2f872be..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class InlineLengthsEncryptedChunkEncoderTest {
-
-    private static final byte[] TEST_NONCE =
-            Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
-    private static final byte[] TEST_KEY_DATA =
-            Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
-    private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
-
-    @Mock private BackupWriter mMockBackupWriter;
-    private ChunkHash mTestKey;
-    private EncryptedChunk mTestChunk;
-    private EncryptedChunkEncoder mEncoder;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockBackupWriter = mock(BackupWriter.class);
-        mTestKey = new ChunkHash(TEST_KEY_DATA);
-        mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
-        mEncoder = new InlineLengthsEncryptedChunkEncoder();
-    }
-
-    @Test
-    public void writeChunkToWriter_writesLengthThenNonceThenData() throws Exception {
-        mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
-
-        InOrder inOrder = inOrder(mMockBackupWriter);
-        inOrder.verify(mMockBackupWriter)
-                .writeBytes(
-                        InlineLengthsEncryptedChunkEncoder.toByteArray(
-                                TEST_NONCE.length + TEST_DATA.length));
-        inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
-        inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
-    }
-
-    @Test
-    public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
-        int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
-
-        assertThat(encodedLength).isEqualTo(Integer.BYTES + TEST_NONCE.length + TEST_DATA.length);
-    }
-
-    @Test
-    public void getChunkOrderingType_returnsExplicitStartsType() {
-        assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.INLINE_LENGTHS);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
deleted file mode 100644
index 978bddb..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.robolectric.RobolectricTestRunner;
-
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class LengthlessEncryptedChunkEncoderTest {
-    private static final byte[] TEST_NONCE =
-            Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES);
-    private static final byte[] TEST_KEY_DATA =
-            Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES);
-    private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9};
-
-    @Mock private BackupWriter mMockBackupWriter;
-    private ChunkHash mTestKey;
-    private EncryptedChunk mTestChunk;
-    private EncryptedChunkEncoder mEncoder;
-
-    @Before
-    public void setUp() throws Exception {
-        mMockBackupWriter = mock(BackupWriter.class);
-        mTestKey = new ChunkHash(TEST_KEY_DATA);
-        mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA);
-        mEncoder = new LengthlessEncryptedChunkEncoder();
-    }
-
-    @Test
-    public void writeChunkToWriter_writesNonceThenData() throws Exception {
-        mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk);
-
-        InOrder inOrder = inOrder(mMockBackupWriter);
-        inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE);
-        inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA);
-    }
-
-    @Test
-    public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() {
-        int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk);
-
-        assertThat(encodedLength).isEqualTo(TEST_NONCE.length + TEST_DATA.length);
-    }
-
-    @Test
-    public void getChunkOrderingType_returnsExplicitStartsType() {
-        assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.EXPLICIT_STARTS);
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
deleted file mode 100644
index 19ef8fb..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunking;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.google.common.primitives.Bytes;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayOutputStream;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class RawBackupWriterTest {
-    private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6};
-
-    private BackupWriter mWriter;
-    private ByteArrayOutputStream mOutput;
-
-    @Before
-    public void setUp() {
-        mOutput = new ByteArrayOutputStream();
-        mWriter = new RawBackupWriter(mOutput);
-    }
-
-    @Test
-    public void writeBytes_writesToOutputStream() throws Exception {
-        mWriter.writeBytes(TEST_BYTES);
-
-        assertThat(mOutput.toByteArray())
-                .asList()
-                .containsExactlyElementsIn(Bytes.asList(TEST_BYTES))
-                .inOrder();
-    }
-
-    @Test
-    public void writeChunk_throwsUnsupportedOperationException() throws Exception {
-        assertThrows(UnsupportedOperationException.class, () -> mWriter.writeChunk(0, 0));
-    }
-
-    @Test
-    public void getBytesWritten_returnsTotalSum() throws Exception {
-        mWriter.writeBytes(TEST_BYTES);
-        mWriter.writeBytes(TEST_BYTES);
-
-        long bytesWritten = mWriter.getBytesWritten();
-
-        assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length);
-    }
-
-    @Test
-    public void flush_flushesOutputStream() throws Exception {
-        mOutput = mock(ByteArrayOutputStream.class);
-        mWriter = new RawBackupWriter(mOutput);
-
-        mWriter.flush();
-
-        verify(mOutput).flush();
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java
deleted file mode 100644
index 0428796..0000000
--- a/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.testing;
-
-import java.security.NoSuchAlgorithmException;
-import java.util.Random;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-/** Helpers for crypto code tests. */
-public class CryptoTestUtils {
-    private static final String KEY_ALGORITHM = "AES";
-    private static final int KEY_SIZE_BITS = 256;
-
-    private CryptoTestUtils() {}
-
-    public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
-        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
-        keyGenerator.init(KEY_SIZE_BITS);
-        return keyGenerator.generateKey();
-    }
-
-    /** Generates a byte array of size {@code n} containing random bytes. */
-    public static byte[] generateRandomBytes(int n) {
-        byte[] bytes = new byte[n];
-        Random random = new Random();
-        random.nextBytes(bytes);
-        return bytes;
-    }
-}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index ad94e61..8699669 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -25,6 +25,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "truth-prebuilt",
+        "testables",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 9670899..1685b04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1486,6 +1486,45 @@
                 SCHED_GROUP_DEFAULT);
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoAll_ServiceB() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        long now = SystemClock.uptimeMillis();
+        ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class));
+        s.startRequested = true;
+        s.lastActivity = now;
+        s = bindService(app2, app, null, 0, mock(IBinder.class));
+        s.startRequested = true;
+        s.lastActivity = now;
+        ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        s = mock(ServiceRecord.class);
+        s.app = app3;
+        setFieldValue(ServiceRecord.class, s, "connections",
+                new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
+        app3.services.add(s);
+        doCallRealMethod().when(s).getConnections();
+        s.startRequested = true;
+        s.lastActivity = now;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        lru.clear();
+        lru.add(app3);
+        lru.add(app2);
+        lru.add(app);
+        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mOomAdjuster.mNumServiceProcs = 3;
+        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        lru.clear();
+
+        assertEquals(SERVICE_B_ADJ, app3.setAdj);
+        assertEquals(SERVICE_ADJ, app2.setAdj);
+        assertEquals(SERVICE_ADJ, app.setAdj);
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         long now = SystemClock.uptimeMillis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
new file mode 100644
index 0000000..307092d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
+
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.WallpaperManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ServiceInfo;
+import android.hardware.display.DisplayManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.service.wallpaper.IWallpaperConnection;
+import android.service.wallpaper.WallpaperService;
+import android.testing.TestableContext;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.wallpaper.WallpaperManagerService.WallpaperData;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Tests for the {@link WallpaperManagerService} class.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:WallpaperManagerServiceTests
+ */
+@Presubmit
+@FlakyTest(bugId = 129797242)
+@RunWith(AndroidJUnit4.class)
+public class WallpaperManagerServiceTests {
+    private static final int DISPLAY_SIZE_DIMENSION = 100;
+    private static StaticMockitoSession sMockitoSession;
+
+    @ClassRule
+    public static final TestableContext sContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    private static ComponentName sImageWallpaperComponentName;
+    private static ComponentName sDefaultWallpaperComponent;
+
+    private IPackageManager mIpm = AppGlobals.getPackageManager();
+
+    @Mock
+    private DisplayManager mDisplayManager;
+
+    @Rule
+    public final TemporaryFolder mFolder = new TemporaryFolder();
+    private final SparseArray<File> mTempDirs = new SparseArray<>();
+    private WallpaperManagerService mService;
+
+    @BeforeClass
+    public static void setUpClass() {
+        sMockitoSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(LocalServices.class)
+                .spyStatic(WallpaperManager.class)
+                .startMocking();
+
+        final WindowManagerInternal dmi = mock(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, dmi);
+
+        sContext.addMockSystemService(Context.APP_OPS_SERVICE, mock(AppOpsManager.class));
+
+        spyOn(sContext);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+                PackageManager.PERMISSION_GRANTED);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SET_WALLPAPER,
+                PackageManager.PERMISSION_GRANTED);
+        doNothing().when(sContext).sendBroadcastAsUser(any(), any());
+
+        //Wallpaper components
+        final IWallpaperConnection.Stub wallpaperService = mock(IWallpaperConnection.Stub.class);
+        sImageWallpaperComponentName = ComponentName.unflattenFromString(
+                sContext.getResources().getString(R.string.image_wallpaper_component));
+        // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
+        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
+
+        if (sDefaultWallpaperComponent == null) {
+            sDefaultWallpaperComponent = sImageWallpaperComponentName;
+            doReturn(sImageWallpaperComponentName).when(() ->
+                    WallpaperManager.getDefaultWallpaperComponent(any()));
+        } else {
+            sContext.addMockService(sDefaultWallpaperComponent, wallpaperService);
+        }
+
+        sContext.addMockService(sImageWallpaperComponentName, wallpaperService);
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (sMockitoSession != null) {
+            sMockitoSession.finishMocking();
+            sMockitoSession = null;
+        }
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        sImageWallpaperComponentName = null;
+        sDefaultWallpaperComponent = null;
+        reset(sContext);
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+
+        final Display mockDisplay = mock(Display.class);
+        doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
+        doReturn(mockDisplay).when(mDisplayManager).getDisplay(anyInt());
+
+        final Display[] displays = new Display[]{mockDisplay};
+        doReturn(displays).when(mDisplayManager).getDisplays();
+
+        spyOn(mIpm);
+        mService = new TestWallpaperManagerService(sContext);
+        spyOn(mService);
+        mService.systemReady();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WallpaperManagerInternal.class);
+
+        mTempDirs.clear();
+        reset(mIpm);
+        mService = null;
+    }
+
+    protected class TestWallpaperManagerService extends WallpaperManagerService {
+        private static final String TAG = "TestWallpaperManagerService";
+
+        TestWallpaperManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        File getWallpaperDir(int userId) {
+            File tempDir = mTempDirs.get(userId);
+            if (tempDir == null) {
+                try {
+                    tempDir = mFolder.newFolder(String.valueOf(userId));
+                    mTempDirs.append(userId, tempDir);
+                } catch (IOException e) {
+                    Log.e(TAG, "getWallpaperDir failed at userId= " + userId);
+                }
+            }
+            return tempDir;
+        }
+
+        // Always return true for test
+        @Override
+        public boolean isWallpaperSupported(String callingPackage) {
+            return true;
+        }
+
+        // Always return true for test
+        @Override
+        public boolean isSetWallpaperAllowed(String callingPackage) {
+            return true;
+        }
+    }
+
+    /**
+     * Tests that internal basic data should be correct after boot up.
+     */
+    @Test
+    public void testDataCorrectAfterBoot() {
+        mService.switchUser(UserHandle.USER_SYSTEM, null);
+
+        final WallpaperData fallbackData = mService.mFallbackWallpaper;
+        assertEquals("Fallback wallpaper component should be ImageWallpaper.",
+                sImageWallpaperComponentName, fallbackData.wallpaperComponent);
+
+        verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+        verifyDisplayData();
+    }
+
+    /**
+     * Tests setWallpaperComponent and clearWallpaper should work as expected.
+     */
+    @Test
+    public void testSetThenClearComponent() {
+        // Skip if there is no pre-defined default wallpaper component.
+        assumeThat(sDefaultWallpaperComponent,
+                not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+        final int testUserId = UserHandle.USER_SYSTEM;
+        mService.switchUser(testUserId, null);
+        verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
+        verifyCurrentSystemData(testUserId);
+
+        mService.setWallpaperComponent(sImageWallpaperComponentName);
+        verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+        verifyCurrentSystemData(testUserId);
+
+        mService.clearWallpaper(null, FLAG_SYSTEM, testUserId);
+        verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
+        verifyCurrentSystemData(testUserId);
+    }
+
+    /**
+     * Tests internal data should be correct and no crash after switch user continuously.
+     */
+    @Test
+    public void testSwitchMultipleUsers() throws Exception {
+        final int lastUserId = 5;
+        final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
+                PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
+        doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
+
+        final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        final ParceledListSlice ris =
+                mIpm.queryIntentServices(intent,
+                        intent.resolveTypeIfNeeded(sContext.getContentResolver()),
+                        PackageManager.GET_META_DATA, 0);
+        doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), anyInt());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
+                eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
+
+        for (int userId = 0; userId <= lastUserId; userId++) {
+            mService.switchUser(userId, null);
+            verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
+            verifyCurrentSystemData(userId);
+        }
+        verifyNoConnectionBeforeLastUser(lastUserId);
+    }
+
+    /**
+     * Tests internal data should be correct and no crash after switch user + unlock user
+     * continuously.
+     * Simulating that the selected WallpaperService is not built-in. After switching users, the
+     * service should not be bound, but bound to the image wallpaper. After receiving the user
+     * unlock callback and can find the selected service for the user, the selected service should
+     * be bound.
+     */
+    @Test
+    public void testSwitchThenUnlockMultipleUsers() throws Exception {
+        final int lastUserId = 5;
+        final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
+                PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
+        doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
+
+        final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        final ParceledListSlice ris =
+                mIpm.queryIntentServices(intent,
+                        intent.resolveTypeIfNeeded(sContext.getContentResolver()),
+                        PackageManager.GET_META_DATA, 0);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
+                eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
+
+        for (int userId = 1; userId <= lastUserId; userId++) {
+            mService.switchUser(userId, null);
+            verifyLastWallpaperData(userId, sImageWallpaperComponentName);
+            // Simulate user unlocked
+            doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), eq(userId));
+            mService.onUnlockUser(userId);
+            verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
+            verifyCurrentSystemData(userId);
+        }
+        verifyNoConnectionBeforeLastUser(lastUserId);
+        verifyDisplayData();
+    }
+
+    // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
+    // non-current user must not bind to wallpaper service.
+    private void verifyNoConnectionBeforeLastUser(int lastUserId) {
+        for (int i = 0; i < lastUserId; i++) {
+            final WallpaperData userData = mService.getCurrentWallpaperData(FLAG_SYSTEM, i);
+            assertNull("No user data connection left", userData.connection);
+        }
+    }
+
+    private void verifyLastWallpaperData(int lastUserId, ComponentName expectedComponent) {
+        final WallpaperData lastData = mService.mLastWallpaper;
+        assertNotNull("Last wallpaper must not be null", lastData);
+        assertEquals("Last wallpaper component must be equals.", expectedComponent,
+                lastData.wallpaperComponent);
+        assertEquals("The user id in last wallpaper should be the last switched user",
+                lastUserId, lastData.userId);
+        assertNotNull("Must exist user data connection on last wallpaper data",
+                lastData.connection);
+    }
+
+    private void verifyCurrentSystemData(int userId) {
+        final WallpaperData lastData = mService.mLastWallpaper;
+        final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, userId);
+        assertEquals("Last wallpaper should be equals to current system wallpaper",
+                lastData, wallpaper);
+    }
+
+    private void verifyDisplayData() {
+        mService.forEachDisplayData(data -> {
+            assertTrue("Display width must larger than maximum screen size",
+                    data.mWidth >= DISPLAY_SIZE_DIMENSION);
+            assertTrue("Display height must larger than maximum screen size",
+                    data.mHeight >= DISPLAY_SIZE_DIMENSION);
+        });
+    }
+}
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 356423a..73dcb98 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -111,6 +111,8 @@
     private static final String VIEW_TEXT = "test_view_text";
     private static final int WINDOWID = 12;
     private static final int PIP_WINDOWID = 13;
+    private static final int WINDOWID_ONSECONDDISPLAY = 14;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
     private static final int SERVICE_ID = 42;
     private static final int A11Y_SERVICE_CAPABILITY = CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
             | CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
@@ -135,6 +137,7 @@
     private AbstractAccessibilityServiceConnection mServiceConnection;
     private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
     private final List<AccessibilityWindowInfo> mA11yWindowInfos = new ArrayList<>();
+    private final List<AccessibilityWindowInfo> mA11yWindowInfosOnSecondDisplay = new ArrayList<>();
     private Callable[] mFindA11yNodesFunctions;
     private Callable<Boolean> mPerformA11yAction;
 
@@ -177,14 +180,22 @@
         when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
 
         // Fake a11yWindowInfo and remote a11y connection for tests.
-        addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false);
-        addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true);
+        addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY);
+        addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY);
+        addA11yWindowInfo(mA11yWindowInfosOnSecondDisplay, WINDOWID_ONSECONDDISPLAY, false,
+                SECONDARY_DISPLAY_ID);
         when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY))
                 .thenReturn(mA11yWindowInfos);
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(0));
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(1));
+        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+            WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
+        when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
+            .thenReturn(mA11yWindowInfosOnSecondDisplay);
+        when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID_ONSECONDDISPLAY))
+            .thenReturn(mA11yWindowInfosOnSecondDisplay.get(0));
         final RemoteAccessibilityConnection conn = getRemoteA11yConnection(
                 WINDOWID, mMockIA11yInteractionConnection, PACKAGE_NAME1);
         final RemoteAccessibilityConnection connPip = getRemoteA11yConnection(
@@ -327,6 +338,12 @@
     }
 
     @Test
+    public void getWindow_onNonDefaultDisplay() {
+        assertThat(mServiceConnection.getWindow(WINDOWID_ONSECONDDISPLAY),
+                is(mA11yWindowInfosOnSecondDisplay.get(0)));
+    }
+
+    @Test
     public void accessAccessibilityNodeInfo_whenCantGetInfo_returnNullOrFalse()
             throws Exception {
         when(mMockSecurityPolicy.canGetAccessibilityNodeInfoLocked(
@@ -674,9 +691,10 @@
     }
 
     private AccessibilityWindowInfo addA11yWindowInfo(List<AccessibilityWindowInfo> infos,
-            int windowId, boolean isPip) {
+            int windowId, boolean isPip, int displayId) {
         final AccessibilityWindowInfo info = AccessibilityWindowInfo.obtain();
         info.setId(windowId);
+        info.setDisplayId(displayId);
         info.setPictureInPicture(isPip);
         infos.add(info);
         return info;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
index e72e460..c73be6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
@@ -35,6 +35,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 /**
  * Tests for GlobalActionPerformer
  */
@@ -84,6 +86,6 @@
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
                 eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
-                anyBoolean(), any(Handler.class));
+                anyBoolean(), any(Handler.class), any());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 9926a09..322653b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -28,12 +28,12 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
+import android.os.IBinder;
 import android.view.KeyEvent;
 
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowManagerPolicy.WindowState;
 
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
@@ -79,7 +79,7 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysUseIt_eventGoesToAms() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L);
         mInterceptor.onKeyEvent(event, 0);
         verify(mMockAms).notifyKeyEvent(argThat(matchesKeyEvent(event)), eq(0));
@@ -88,7 +88,7 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDropIt_eventDropped() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
         mInterceptor.onKeyEvent(event, 0);
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
@@ -98,14 +98,14 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDelayThenUse_eventQueuedThenSentToAms() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(150L);
         mInterceptor.onKeyEvent(event, 0);
 
         assertTrue(mHandler.hasMessages());
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(0L);
         mHandler.sendAllMessages();
 
@@ -115,14 +115,14 @@
     @Test
     public void whenVolumeKeyArrives_andPolicySaysDelayThenDrop_eventQueuedThenDropped() {
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(150L);
         mInterceptor.onKeyEvent(event, 0);
 
         assertTrue(mHandler.hasMessages());
         verify(mMockAms, times(0)).notifyKeyEvent(anyObject(), anyInt());
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(event)), eq(0))).thenReturn(-1L);
         mHandler.sendAllMessages();
 
@@ -137,18 +137,18 @@
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP),
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)};
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(150L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(75L);
 
         for (KeyEvent event : events) {
             mInterceptor.onKeyEvent(event, 0);
         }
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(0L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(0L);
 
         mHandler.sendAllMessages();
@@ -167,18 +167,18 @@
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP),
                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0)};
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(150L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(75L);
 
         for (KeyEvent event : events) {
             mInterceptor.onKeyEvent(event, 0);
         }
 
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[1])), eq(0))).thenReturn(-1L);
-        when(mMockPolicy.interceptKeyBeforeDispatching((WindowState) argThat(nullValue()),
+        when(mMockPolicy.interceptKeyBeforeDispatching((IBinder) argThat(nullValue()),
                 argThat(matchesKeyEvent(events[3])), eq(0))).thenReturn(-1L);
 
         mHandler.sendAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
index 2585a28..b707912 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
@@ -176,6 +176,6 @@
 
         // Check that correct gesture was recognized.
         verify(mResultListener).onGestureCompleted(
-                argThat(gestureInfo -> gestureInfo.getGestureId() == gestureId));
+                argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 274ca36..104aacb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -16,15 +16,18 @@
 
 package com.android.server.accessibility.gestures;
 
+import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR;
+import static com.android.server.accessibility.gestures.TouchState.STATE_DELEGATING;
+import static com.android.server.accessibility.gestures.TouchState.STATE_DRAGGING;
+import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.graphics.PointF;
 import android.os.SystemClock;
 import android.testing.DexmakerShareClassLoaderRule;
-import android.util.DebugUtils;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 
@@ -46,10 +49,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
-    public static final int STATE_TOUCH_EXPLORING = 0x00000001;
-    public static final int STATE_DRAGGING = 0x00000002;
-    public static final int STATE_DELEGATING = 0x00000004;
-
     private static final int FLAG_1FINGER = 0x8000;
     private static final int FLAG_2FINGERS = 0x0100;
     private static final int FLAG_3FINGERS = 0x0200;
@@ -112,7 +111,7 @@
 
     @Test
     public void testTwoFingersMove_shouldDelegatingAndInjectActionDownPointerDown() {
-        goFromStateIdleTo(STATE_MOVING_2FINGERS);
+        goFromStateClearTo(STATE_MOVING_2FINGERS);
 
         assertState(STATE_DELEGATING);
         assertCapturedEvents(
@@ -123,7 +122,7 @@
 
     @Test
     public void testTwoFingersDrag_shouldDraggingAndActionDown() {
-        goFromStateIdleTo(STATE_DRAGGING_2FINGERS);
+        goFromStateClearTo(STATE_DRAGGING_2FINGERS);
 
         assertState(STATE_DRAGGING);
         assertCapturedEvents(MotionEvent.ACTION_DOWN);
@@ -133,7 +132,7 @@
     @Test
     public void testTwoFingersNotDrag_shouldDelegatingAndActionUpDownPointerDown() {
         // only from dragging state, and withMoveHistory no dragging
-        goFromStateIdleTo(STATE_PINCH_2FINGERS);
+        goFromStateClearTo(STATE_PINCH_2FINGERS);
 
         assertState(STATE_DELEGATING);
         assertCapturedEvents(
@@ -146,7 +145,7 @@
 
     @Test
     public void testThreeFingersMove_shouldDelegatingAnd3ActionPointerDown() {
-        goFromStateIdleTo(STATE_MOVING_3FINGERS);
+        goFromStateClearTo(STATE_MOVING_3FINGERS);
 
         assertState(STATE_DELEGATING);
         assertCapturedEvents(
@@ -165,52 +164,47 @@
         return new PointF(x, y);
     }
 
-    private static String stateToString(int state) {
-        return DebugUtils.valueToString(TouchExplorerTest.class, "STATE_", state);
-    }
-
-    private void goFromStateIdleTo(int state) {
+    private void goFromStateClearTo(int state) {
         try {
             switch (state) {
-                case STATE_TOUCH_EXPLORING: {
+                case STATE_CLEAR: {
                     mTouchExplorer.onDestroy();
                 }
                 break;
                 case STATE_TOUCH_EXPLORING_1FINGER: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING);
                     send(downEvent());
                 }
                 break;
                 case STATE_TOUCH_EXPLORING_2FINGER: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING_1FINGER);
+                    goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
                     send(pointerDownEvent());
                 }
                 break;
                 case STATE_TOUCH_EXPLORING_3FINGER: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
+                    goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER);
                     send(thirdPointerDownEvent());
                 }
                 break;
                 case STATE_MOVING_2FINGERS: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
+                    goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER);
                     moveEachPointers(mLastEvent, p(10, 0), p(5, 10));
                     send(mLastEvent);
                 }
                 break;
                 case STATE_DRAGGING_2FINGERS: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING_2FINGER);
+                    goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER);
                     moveEachPointers(mLastEvent, p(10, 0), p(10, 0));
                     send(mLastEvent);
                 }
                 break;
                 case STATE_PINCH_2FINGERS: {
-                    goFromStateIdleTo(STATE_DRAGGING_2FINGERS);
+                    goFromStateClearTo(STATE_DRAGGING_2FINGERS);
                     moveEachPointers(mLastEvent, p(10, 0), p(-10, 1));
                     send(mLastEvent);
                 }
                 break;
                 case STATE_MOVING_3FINGERS: {
-                    goFromStateIdleTo(STATE_TOUCH_EXPLORING_3FINGER);
+                    goFromStateClearTo(STATE_TOUCH_EXPLORING_3FINGER);
                     moveEachPointers(mLastEvent, p(1, 0), p(1, 0), p(1, 0));
                     send(mLastEvent);
                 }
@@ -219,7 +213,8 @@
                     throw new IllegalArgumentException("Illegal state: " + state);
             }
         } catch (Throwable t) {
-            throw new RuntimeException("Failed to go to state " + stateToString(state), t);
+            throw new RuntimeException("Failed to go to state "
+            + TouchState.getStateSymbolicName(state), t);
         }
     }
 
@@ -234,9 +229,9 @@
     }
 
     private void assertState(int expect) {
-        final String expectState = "STATE_" + stateToString(expect);
-        assertTrue(String.format("Expect state: %s, but: %s", expectState, mTouchExplorer),
-                mTouchExplorer.toString().contains(expectState));
+        assertEquals(
+                TouchState.getStateSymbolicName(expect),
+                TouchState.getStateSymbolicName(mTouchExplorer.getState().getState()));
     }
 
     private void assertCapturedEvents(int... actionsInOrder) {
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
index 6678a78..9e3b54d 100644
--- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -20,21 +20,14 @@
 import static com.android.server.am.MemoryStatUtil.JIFFY_NANOS;
 import static com.android.server.am.MemoryStatUtil.MemoryStat;
 import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs;
-import static com.android.server.am.MemoryStatUtil.parseIonHeapSizeFromDebugfs;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
 import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.parseProcessIonHeapSizesFromDebugfs;
-import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs;
-
-import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.am.MemoryStatUtil.IonAllocations;
-
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
@@ -183,71 +176,6 @@
             + "voluntary_ctxt_switches:\t903\n"
             + "nonvoluntary_ctxt_switches:\t104\n";
 
-    // Repeated lines have been removed.
-    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join(
-            "\n",
-            "          client              pid             size",
-            "----------------------------------------------------",
-            " audio@2.0-servi              765             4096",
-            " audio@2.0-servi              765            61440",
-            " audio@2.0-servi              765             4096",
-            "     voip_client               96             8192",
-            "     voip_client               96             4096",
-            "   system_server             1232         16728064",
-            "  surfaceflinger              611         50642944",
-            "----------------------------------------------------",
-            "orphaned allocations (info is from last known client):",
-            "----------------------------------------------------",
-            "  total orphaned                0",
-            "          total          55193600",
-            "   deferred free                0",
-            "----------------------------------------------------",
-            "0 order 4 highmem pages in uncached pool = 0 total",
-            "0 order 4 lowmem pages in uncached pool = 0 total",
-            "1251 order 4 lowmem pages in cached pool = 81985536 total",
-            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
-            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
-            "--------------------------------------------",
-            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
-            "pool total (uncached + cached + secure) = 83570688",
-            "--------------------------------------------");
-
-    // Repeated lines have been removed.
-    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join(
-            "\n",
-            "          client              pid             size      page counts"
-                    + "--------------------------------------------------       4K       8K      "
-                    + "16K      32K      64K     128K     256K     512K       1M       2M       "
-                    + "4M      >=8M",
-            "   system_server             1705         58097664    13120      532        "
-                    + "0        0        0        0        0        0        0        0        "
-                    + "0        0M",
-            " audio@2.0-servi              851            16384        0        2        0        "
-                    + "0        0        0        0        0        0        0        "
-                    + "0        0M",
-            " audio@2.0-servi              851             4096        1        0        0       "
-                    + " 0        0        0        0        0        0        0        0        "
-                    + "0M",
-            " audio@2.0-servi              851             4096        1        0      "
-                    + "  0        0        0        0        0        0        0        0        "
-                    + "0        0M",
-            "----------------------------------------------------",
-            "orphaned allocations (info is from last known client):",
-            "----------------------------------------------------",
-            "  total orphaned                0",
-            "          total         159928320",
-            "   deferred free                0",
-            "----------------------------------------------------",
-            "0 order 4 highmem pages in uncached pool = 0 total",
-            "0 order 4 lowmem pages in uncached pool = 0 total",
-            "1251 order 4 lowmem pages in cached pool = 81985536 total",
-            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
-            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
-            "--------------------------------------------",
-            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
-            "pool total (uncached + cached + secure) = 83570688",
-            "--------------------------------------------");
-
     @Test
     public void testParseMemoryStatFromMemcg_parsesCorrectValues() {
         MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS);
@@ -301,18 +229,6 @@
     }
 
     @Test
-    public void testParseVmHWMFromProcfs_parsesCorrectValue() {
-        assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS) / BYTES_IN_KILOBYTE);
-    }
-
-    @Test
-    public void testParseVmHWMFromProcfs_emptyContents() {
-        assertEquals(0, parseVmHWMFromProcfs(""));
-
-        assertEquals(0, parseVmHWMFromProcfs(null));
-    }
-
-    @Test
     public void testParseCmdlineFromProcfs_invalidValue() {
         byte[] nothing = new byte[] {0x00, 0x74, 0x65, 0x73, 0x74}; // \0test
 
@@ -348,65 +264,4 @@
         output.write(bytes, 0, bytes.length);
         return output.toString();
     }
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_emptyContents() {
-        assertEquals(0, parseIonHeapSizeFromDebugfs(""));
-
-        assertEquals(0, parseIonHeapSizeFromDebugfs(null));
-    }
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_invalidValue() {
-        assertEquals(0, parseIonHeapSizeFromDebugfs("<<no-value>>"));
-
-        assertEquals(0, parseIonHeapSizeFromDebugfs("\ntotal 12345678901234567890\n"));
-    }
-
-    @Test
-    public void testParseIonHeapSizeFromDebugfs_correctValue() {
-        assertEquals(55193600, parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS));
-
-        assertEquals(159928320, parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO));
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() {
-        assertEquals(0, parseProcessIonHeapSizesFromDebugfs("").size());
-
-        assertEquals(0, parseProcessIonHeapSizesFromDebugfs(null).size());
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() {
-        assertEquals(0, parseProcessIonHeapSizesFromDebugfs("<<no-value>>").size());
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
-                .containsExactly(
-                        createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440),
-                        createIonAllocations(96, 8192 + 4096, 2, 8192),
-                        createIonAllocations(1232, 16728064, 1, 16728064),
-                        createIonAllocations(611, 50642944, 1, 50642944));
-    }
-
-    @Test
-    public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() {
-        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
-                .containsExactly(
-                        createIonAllocations(1705, 58097664, 1, 58097664),
-                        createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384));
-    }
-
-    private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count,
-            long maxSizeInBytes) {
-        IonAllocations allocations = new IonAllocations();
-        allocations.pid = pid;
-        allocations.totalSizeInBytes = totalSizeInBytes;
-        allocations.count = count;
-        allocations.maxSizeInBytes = maxSizeInBytes;
-        return allocations;
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 66d2bab..70650de 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -89,13 +89,11 @@
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for(int i = 0; i < uidStates.size(); i++) {
             final AppOpsService.UidState uidState = uidStates.valueAt(i);
-            if (uidState.opModes != null) {
-                final int uidMode1 = uidState.opModes.get(op1, defaultModeOp1);
-                final int uidMode2 = uidState.opModes.get(op2, defaultModeOp2);
-                assertEquals(uidMode1, uidMode2);
-                if (uidMode1 != defaultModeOp1) {
-                    numberOfNonDefaultOps++;
-                }
+            final int uidMode1 = uidState.hasOpMode(op1) ? uidState.getOpMode(op1) : defaultModeOp1;
+            final int uidMode2 = uidState.hasOpMode(op2) ? uidState.getOpMode(op2) : defaultModeOp2;
+            assertEquals(uidMode1, uidMode2);
+            if (uidMode1 != defaultModeOp1) {
+                numberOfNonDefaultOps++;
             }
             if (uidState.pkgOps == null) {
                 continue;
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java
new file mode 100644
index 0000000..bae11eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.transport;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.backup.IBackupTransport;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DelegatingTransportTest {
+    @Mock private IBackupTransport mBackupTransport;
+    @Mock private PackageInfo mPackageInfo;
+    @Mock private ParcelFileDescriptor mFd;
+
+    private final String mPackageName = "testpackage";
+    private final RestoreSet mRestoreSet = new RestoreSet();
+    private final int mFlags = 1;
+    private final long mRestoreToken = 10;
+    private final long mSize = 100;
+    private final int mNumBytes = 1000;
+    private DelegatingTransport mDelegatingTransport;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mDelegatingTransport = new DelegatingTransport() {
+            @Override
+            protected IBackupTransport getDelegate() {
+                return mBackupTransport;
+            }
+        };
+    }
+
+    @Test
+    public void testName() throws RemoteException {
+        String exp = "dummy";
+        when(mBackupTransport.name()).thenReturn(exp);
+
+        String ret = mDelegatingTransport.name();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).name();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testConfigurationIntent() throws RemoteException {
+        Intent exp = new Intent("dummy");
+        when(mBackupTransport.configurationIntent()).thenReturn(exp);
+
+        Intent ret = mDelegatingTransport.configurationIntent();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).configurationIntent();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testCurrentDestinationString() throws RemoteException {
+        String exp = "dummy";
+        when(mBackupTransport.currentDestinationString()).thenReturn(exp);
+
+        String ret = mDelegatingTransport.currentDestinationString();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).currentDestinationString();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testDataManagementIntent() throws RemoteException {
+        Intent exp = new Intent("dummy");
+        when(mBackupTransport.dataManagementIntent()).thenReturn(exp);
+
+        Intent ret = mDelegatingTransport.dataManagementIntent();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).dataManagementIntent();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testDataManagementIntentLabel() throws RemoteException {
+        String exp = "dummy";
+        when(mBackupTransport.dataManagementIntentLabel()).thenReturn(exp);
+
+        CharSequence ret = mDelegatingTransport.dataManagementIntentLabel();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).dataManagementIntentLabel();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testTransportDirName() throws RemoteException {
+        String exp = "dummy";
+        when(mBackupTransport.transportDirName()).thenReturn(exp);
+
+        String ret = mDelegatingTransport.transportDirName();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).transportDirName();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testRequestBackupTime() throws RemoteException {
+        long exp = 1000L;
+        when(mBackupTransport.requestBackupTime()).thenReturn(exp);
+
+        long ret = mDelegatingTransport.requestBackupTime();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).requestBackupTime();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testInitializeDevice() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.initializeDevice()).thenReturn(exp);
+
+        long ret = mDelegatingTransport.initializeDevice();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).initializeDevice();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testPerformBackup() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.performBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.performBackup(mPackageInfo, mFd, mFlags);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).performBackup(mPackageInfo, mFd, mFlags);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testClearBackupData() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.clearBackupData(mPackageInfo)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.clearBackupData(mPackageInfo);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).clearBackupData(mPackageInfo);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testFinishBackup() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.finishBackup()).thenReturn(exp);
+
+        int ret = mDelegatingTransport.finishBackup();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).finishBackup();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetAvailableRestoreSets() throws RemoteException {
+        RestoreSet[] exp = new RestoreSet[] {mRestoreSet};
+        when(mBackupTransport.getAvailableRestoreSets()).thenReturn(exp);
+
+        RestoreSet[] ret = mDelegatingTransport.getAvailableRestoreSets();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getAvailableRestoreSets();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetCurrentRestoreSet() throws RemoteException {
+        long exp = 1000;
+        when(mBackupTransport.getCurrentRestoreSet()).thenReturn(exp);
+
+        long ret = mDelegatingTransport.getCurrentRestoreSet();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getCurrentRestoreSet();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testStartRestore() throws RemoteException {
+        int exp = 1000;
+        PackageInfo[] packageInfos = {mPackageInfo};
+        when(mBackupTransport.startRestore(mRestoreToken, packageInfos)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.startRestore(mRestoreToken, packageInfos);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).startRestore(mRestoreToken, packageInfos);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testNextRestorePackage() throws RemoteException {
+        RestoreDescription exp = new RestoreDescription(mPackageName, 1);
+        when(mBackupTransport.nextRestorePackage()).thenReturn(exp);
+
+        RestoreDescription ret = mDelegatingTransport.nextRestorePackage();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).nextRestorePackage();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetRestoreData() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.getRestoreData(mFd)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.getRestoreData(mFd);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getRestoreData(mFd);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void tesFinishRestore() throws RemoteException {
+        mDelegatingTransport.finishRestore();
+
+        verify(mBackupTransport, times(1)).finishRestore();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testRequestFullBackupTime() throws RemoteException {
+        long exp = 1000L;
+        when(mBackupTransport.requestFullBackupTime()).thenReturn(exp);
+
+        long ret = mDelegatingTransport.requestFullBackupTime();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).requestFullBackupTime();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testPerformFullBackup() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.performFullBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.performFullBackup(mPackageInfo, mFd, mFlags);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).performFullBackup(mPackageInfo, mFd, mFlags);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testCheckFullBackupSize() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.checkFullBackupSize(mSize)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.checkFullBackupSize(mSize);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).checkFullBackupSize(mSize);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testSendBackupData() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.sendBackupData(mNumBytes)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.sendBackupData(mNumBytes);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).sendBackupData(mNumBytes);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testCancelFullBackup() throws RemoteException {
+        mDelegatingTransport.cancelFullBackup();
+
+        verify(mBackupTransport, times(1)).cancelFullBackup();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testIsAppEligibleForBackup() throws RemoteException {
+        boolean exp = true;
+        when(mBackupTransport.isAppEligibleForBackup(mPackageInfo, true)).thenReturn(exp);
+
+        boolean ret = mDelegatingTransport.isAppEligibleForBackup(mPackageInfo, true);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).isAppEligibleForBackup(mPackageInfo, true);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetBackupQuota() throws RemoteException {
+        long exp = 1000;
+        when(mBackupTransport.getBackupQuota(mPackageName, true)).thenReturn(exp);
+
+        long ret = mDelegatingTransport.getBackupQuota(mPackageName, true);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getBackupQuota(mPackageName, true);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetNextFullRestoreDataChunk() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.getNextFullRestoreDataChunk(mFd)).thenReturn(exp);
+
+        int ret = mDelegatingTransport.getNextFullRestoreDataChunk(mFd);
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getNextFullRestoreDataChunk(mFd);
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testAbortFullRestore() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.abortFullRestore()).thenReturn(exp);
+
+        int ret = mDelegatingTransport.abortFullRestore();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).abortFullRestore();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+
+    @Test
+    public void testGetTransportFlags() throws RemoteException {
+        int exp = 1000;
+        when(mBackupTransport.getTransportFlags()).thenReturn(exp);
+
+        int ret = mDelegatingTransport.getTransportFlags();
+
+        assertEquals(exp, ret);
+        verify(mBackupTransport, times(1)).getTransportFlags();
+        verifyNoMoreInteractions(mBackupTransport);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ce83df7..64ea59d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.File;
@@ -210,6 +211,11 @@
         }
 
         @Override
+        LockSettingsInternal getLockSettingsInternal() {
+            return services.lockSettingsInternal;
+        }
+
+        @Override
         IAudioService getIAudioService() {
             return services.iaudioService;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 9ae9824..d900910 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -764,7 +765,38 @@
     }
 
     /**
-     * Test for: @{link DevicePolicyManager#setActivePasswordState}
+     * Test for: {@link DevicePolicyManager#setPasswordHistoryLength(ComponentName, int)}
+     *
+     * Validates that when the password history length is set, it is persisted after rebooting
+     */
+    public void testSaveAndLoadPasswordHistoryLength_persistedAfterReboot() throws Exception {
+        int passwordHistoryLength = 2;
+
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        // Install admin1 on system user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+
+        // Set admin1 to active admin and device owner
+        dpm.setActiveAdmin(admin1, false);
+        dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM);
+
+        // Save password history length
+        dpm.setPasswordHistoryLength(admin1, passwordHistoryLength);
+
+        assertEquals(dpm.getPasswordHistoryLength(admin1), passwordHistoryLength);
+
+        initializeDpms();
+        reset(mContext.spiedContext);
+
+        // Password history length should persist after rebooted
+        assertEquals(dpm.getPasswordHistoryLength(admin1), passwordHistoryLength);
+    }
+
+    /**
+     * Test for: {@link DevicePolicyManager#reportPasswordChanged}
      *
      * Validates that when the password for a user changes, the notification broadcast intent
      * {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is sent to managed profile owners, in
@@ -806,7 +838,7 @@
     }
 
     /**
-     * Test for: @{link DevicePolicyManager#setActivePasswordState}
+     * Test for: @{link DevicePolicyManager#reportPasswordChanged}
      *
      * Validates that when the password for a managed profile changes, the notification broadcast
      * intent {@link DeviceAdminReceiver#ACTION_PASSWORD_CHANGED} is only sent to the profile, not
@@ -4258,6 +4290,10 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         mContext.packageName = admin1.getPackageName();
         setupDeviceOwner();
+        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+        // When there is no lockscreen, user password metrics is always empty.
+        when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
+                .thenReturn(new PasswordMetrics());
 
         // If no password requirements are set, isActivePasswordSufficient should succeed.
         assertTrue(dpm.isActivePasswordSufficient());
@@ -4266,14 +4302,7 @@
         dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
 
         reset(mContext.spiedContext);
-        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
-        PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
-                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
-                8, 2,
-                6, 1,
-                0, 1);
         // This should be ignored, as there is no lock screen.
-        dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
         dpm.reportPasswordChanged(userHandle);
 
         // No broadcast should be sent.
@@ -4290,19 +4319,24 @@
         final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
         final long ident = mContext.binder.clearCallingIdentity();
 
-        dpm.setActivePasswordState(passwordMetrics, userHandle);
+        when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
+                .thenReturn(passwordMetrics);
         dpm.reportPasswordChanged(userHandle);
 
         // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of
         // reportPasswordChanged()
-        // This broadcast should be sent 4 times:
+        // This broadcast should be sent 2-4 times:
         // * Twice from calls to DevicePolicyManagerService.updatePasswordExpirationsLocked,
         //   once for each affected user, in DevicePolicyManagerService.reportPasswordChanged.
-        // * Twice from calls to DevicePolicyManagerService.saveSettingsLocked
+        // * Optionally, at most twice from calls to DevicePolicyManagerService.saveSettingsLocked
         //   in DevicePolicyManagerService.reportPasswordChanged, once with the userId
         //   the password change is relevant to and another with the credential owner of said
-        //   userId.
-        verify(mContext.spiedContext, times(4)).sendBroadcastAsUser(
+        //   userId, if the password checkpoint value changes.
+        verify(mContext.spiedContext, atMost(4)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+        verify(mContext.spiedContext, atLeast(2)).sendBroadcastAsUser(
                 MockUtils.checkIntentAction(
                         DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
                 MockUtils.checkUserHandle(userHandle));
@@ -5224,9 +5258,9 @@
         mServiceContext.permissions.add(permission.REQUEST_PASSWORD_COMPLEXITY);
         when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
                 .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
-        dpms.mUserPasswordMetrics.put(
-                DpmMockContext.CALLER_USER_HANDLE,
-                PasswordMetrics.computeForPassword("asdf".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
 
         assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
     }
@@ -5241,12 +5275,12 @@
         when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
                 .thenReturn(parentUser.id);
 
-        dpms.mUserPasswordMetrics.put(
-                DpmMockContext.CALLER_USER_HANDLE,
-                PasswordMetrics.computeForPassword("asdf".getBytes()));
-        dpms.mUserPasswordMetrics.put(
-                parentUser.id,
-                PasswordMetrics.computeForPassword("parentUser".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+        when(getServices().lockSettingsInternal
+                .getUserPasswordMetrics(parentUser.id))
+                .thenReturn(PasswordMetrics.computeForPassword("parentUser".getBytes()));
 
         assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index f6f365e..b0d0303 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -64,6 +64,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -102,6 +103,7 @@
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
+    public final LockSettingsInternal lockSettingsInternal;
     public final StorageManagerForMock storageManager;
     public final WifiManager wifiManager;
     public final SettingsForMock settings;
@@ -143,6 +145,7 @@
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
+        lockSettingsInternal = mock(LockSettingsInternal.class);
         storageManager = mock(StorageManagerForMock.class);
         wifiManager = mock(WifiManager.class);
         settings = mock(SettingsForMock.class);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 67d6eda..7354ad4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -92,24 +92,18 @@
 
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
         final long sid = 1234;
-        final String FAILED_MESSAGE = "Failed to enroll password";
         initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
 
-        try {
-            mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
-                    "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-            fail("Did not fail when enrolling using incorrect credential");
-        } catch (IllegalStateException expected) {
-            assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
-        }
+        assertFalse(mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
+                    "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
     }
 
     public void testClearPasswordPrimaryUser() throws RemoteException {
         final String PASSWORD = "password";
         initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
-        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+        assertTrue(mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false));
         assertFalse(mService.havePassword(PRIMARY_USER_ID));
         assertFalse(mService.havePattern(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -118,9 +112,9 @@
     public void testManagedProfileUnifiedChallenge() throws RemoteException {
         final String firstUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-1";
         final String secondUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-2";
-        mService.setLockCredential(firstUnifiedPassword.getBytes(),
+        assertTrue(mService.setLockCredential(firstUnifiedPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false);
+                null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -154,17 +148,17 @@
          */
         mStorageManager.setIgnoreBadUnlock(true);
         // Change primary password and verify that profile SID remains
-        mService.setLockCredential(secondUnifiedPassword.getBytes(),
+        assertTrue(mService.setLockCredential(secondUnifiedPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, firstUnifiedPassword.getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Clear unified challenge
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+        assertTrue(mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
                 secondUnifiedPassword.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID,
-                false);
+                false));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -173,17 +167,17 @@
     public void testManagedProfileSeparateChallenge() throws RemoteException {
         final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
         final String profilePassword = "testManagedProfileSeparateChallenge-profile";
-        mService.setLockCredential(primaryPassword.getBytes(),
+        assertTrue(mService.setLockCredential(primaryPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false);
+                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
         /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
          * credential as part of verifyCredential() before the new credential is committed in
          * StorageManager. So we relax the check in our mock StorageManager to allow that.
          */
         mStorageManager.setIgnoreBadUnlock(true);
-        mService.setLockCredential(profilePassword.getBytes(),
+        assertTrue(mService.setLockCredential(profilePassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false);
+                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -209,8 +203,9 @@
 
         // Change primary credential and make sure we don't affect profile
         mStorageManager.setIgnoreBadUnlock(true);
-        mService.setLockCredential("pwd".getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        assertTrue(mService.setLockCredential("pwd".getBytes(),
+                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
@@ -221,13 +216,13 @@
     public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
         final byte[] password = "password".getBytes();
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 password,
                 CREDENTIAL_TYPE_PASSWORD,
                 null,
                 PASSWORD_QUALITY_ALPHABETIC,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
@@ -237,13 +232,13 @@
             throws Exception {
         final byte[] pattern = "12345".getBytes();
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 pattern,
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
@@ -259,13 +254,13 @@
                 CREDENTIAL_TYPE_PATTERN,
                 PASSWORD_QUALITY_SOMETHING);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 newCredential,
                 CREDENTIAL_TYPE_PASSWORD,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_ALPHABETIC,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(
@@ -276,13 +271,13 @@
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 "12345".getBytes(),
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager, never())
                 .lockScreenSecretChanged(
@@ -298,13 +293,13 @@
                 PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 newCredential,
                 CREDENTIAL_TYPE_PASSWORD,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_ALPHABETIC,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID);
@@ -321,13 +316,13 @@
                 PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 null,
                 CREDENTIAL_TYPE_NONE,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_UNSPECIFIED,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID);
@@ -343,13 +338,13 @@
                 PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 profilePassword,
                 CREDENTIAL_TYPE_PASSWORD,
                 null,
                 PASSWORD_QUALITY_ALPHABETIC,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(
@@ -395,13 +390,13 @@
     public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
         final byte[] pattern = "12345".getBytes();
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 pattern,
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
         reset(mRecoverableKeyStoreManager);
 
         mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID);
@@ -436,8 +431,8 @@
 
     private void testCreateCredential(int userId, String credential, int type, int quality)
             throws RemoteException {
-        mService.setLockCredential(credential.getBytes(), type, null, quality,
-                userId, false);
+        assertTrue(mService.setLockCredential(credential.getBytes(), type, null, quality,
+                userId, false));
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
@@ -461,8 +456,8 @@
             String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
         initializeStorageWithCredential(userId, oldCredential, oldType, sid);
-        mService.setLockCredential(newCredential.getBytes(), newType, oldCredential.getBytes(),
-                quality, userId, false);
+        assertTrue(mService.setLockCredential(newCredential.getBytes(), newType,
+                oldCredential.getBytes(), quality, userId, false));
         assertVerifyCredentials(userId, newCredential, newType, sid);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 1cd590c..127cf49 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -341,6 +341,10 @@
         final byte[] pattern = "123654".getBytes();
         final byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        // Disregard any reportPasswordChanged() invocations as part of credential setup.
+        flushHandlerTasks();
+        reset(mDevicePolicyManager);
+
         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
@@ -360,7 +364,8 @@
         flushHandlerTasks();
         final PasswordMetrics metric = PasswordMetrics.computeForCredential(
                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
-        verify(mDevicePolicyManager).setActivePasswordState(metric, PRIMARY_USER_ID);
+        assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
+        verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
index bd3d9ab..3852b9f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.pm.ModuleInfo;
+import android.content.pm.PackageManager;
 import android.test.InstrumentationTestCase;
 
 import com.android.frameworks.servicestests.R;
@@ -28,7 +29,7 @@
     public void testSuccessfulParse() {
         ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
 
-        List<ModuleInfo> mi = provider.getInstalledModules(0);
+        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
         assertEquals(2, mi.size());
 
         Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
@@ -49,18 +50,18 @@
 
     public void testParseFailure_incorrectTopLevelElement() {
         ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
-        assertEquals(0, provider.getInstalledModules(0).size());
+        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
     }
 
     public void testParseFailure_incorrectModuleElement() {
         ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
-        assertEquals(0, provider.getInstalledModules(0).size());
+        assertEquals(0, provider.getInstalledModules(PackageManager.MATCH_ALL).size());
     }
 
     public void testParse_unknownAttributesIgnored() {
         ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
 
-        List<ModuleInfo> mi = provider.getInstalledModules(0);
+        List<ModuleInfo> mi = provider.getInstalledModules(PackageManager.MATCH_ALL);
         assertEquals(2, mi.size());
 
         ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
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 fc7cfec..0a310d1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -118,17 +118,20 @@
         String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
         String[] appdir = { "app", "priv-app" };
         for (int i = 0; i < partitions.length; i++) {
+            final PackageManagerService.SystemPartition systemPartition =
+                    PackageManagerService.SYSTEM_PARTITIONS.get(i);
             for (int j = 0; j < appdir.length; j++) {
                 String canonical = new File("/" + partitions[i]).getCanonicalPath();
                 String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
 
-                Assert.assertEquals(j == 1 && i != 3,
-                    PackageManagerService.locationIsPrivileged(path));
+                Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path));
 
-                Assert.assertEquals(i == 1 || i == 2, PackageManagerService.locationIsVendor(path));
-                Assert.assertEquals(i == 3, PackageManagerService.locationIsOem(path));
-                Assert.assertEquals(i == 4, PackageManagerService.locationIsProduct(path));
-                Assert.assertEquals(i == 5, PackageManagerService.locationIsSystemExt(path));
+                final int scanFlag = systemPartition.scanFlag;
+                Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
+                Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
+                Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
+                Assert.assertEquals(i == 4, scanFlag == PackageManagerService.SCAN_AS_PRODUCT);
+                Assert.assertEquals(i == 5, scanFlag == PackageManagerService.SCAN_AS_SYSTEM_EXT);
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index ff06489..3fe9b52 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -46,6 +46,7 @@
 import android.os.UserManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.LongSparseArray;
 
@@ -53,15 +54,15 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.AtomicFile;
 import com.android.server.LocalServices;
-import com.android.server.pm.permission.PermissionManagerService;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.permission.PermissionSettings;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -78,7 +79,14 @@
     private static final String PACKAGE_NAME_3 = "com.android.app3";
     private static final String PACKAGE_NAME_1 = "com.android.app1";
     public static final String TAG = "PackageManagerSettingsTests";
-    protected final String PREFIX = "android.content.pm";
+
+    @Mock
+    PermissionSettings mPermissionSettings;
+
+    @Before
+    public void initializeMocks() {
+        MockitoAnnotations.initMocks(this);
+    }
 
     /** make sure our initialized KeySetManagerService metadata matches packages.xml */
     @Test
@@ -88,9 +96,7 @@
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
-        Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         verifyKeySetMetaData(settings);
     }
@@ -103,9 +109,7 @@
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
-        Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         // write out, read back in and verify the same
@@ -116,13 +120,11 @@
 
     @Test
     public void testSettingsReadOld() {
-        // Write the package files and make sure they're parsed properly the first time
+        // Write delegateshellthe package files and make sure they're parsed properly the first time
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
-        Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -143,15 +145,12 @@
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
-        Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         settings.writeLPr();
 
         // Create Settings again to make it read from the new files
-        settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -313,9 +312,7 @@
         writeOldFiles();
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
-        Settings settings =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+        Settings settings = new Settings(context.getFilesDir(), mPermissionSettings, lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         // Enable/Disable a package
@@ -507,9 +504,8 @@
     public void testUpdatePackageSetting03() {
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
         final Settings testSettings01 =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+                new Settings(context.getFilesDir(), mPermissionSettings, lock);
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
                 testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 =
@@ -625,9 +621,8 @@
     public void testCreateNewSetting03() {
         final Context context = InstrumentationRegistry.getContext();
         final Object lock = new Object();
-        PermissionManagerServiceInternal pmInt = PermissionManagerService.create(context, lock);
         final Settings testSettings01 =
-                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
+                new Settings(context.getFilesDir(), mPermissionSettings, lock);
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
                 testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 = Settings.createNewSetting(
diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
index d5d32bd..2290ef7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SuspendPackagesTest.java
@@ -18,7 +18,10 @@
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.opToName;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,7 +42,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
 import android.content.res.Resources;
-import android.media.AudioAttributes;
 import android.os.BaseBundle;
 import android.os.Bundle;
 import android.os.Handler;
@@ -57,6 +59,7 @@
 import android.view.WindowManagerGlobal;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -79,6 +82,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @LargeTest
+@FlakyTest
 public class SuspendPackagesTest {
     private static final String TAG = SuspendPackagesTest.class.getSimpleName();
     private static final String TEST_APP_LABEL = "Suspend Test App";
@@ -551,28 +555,42 @@
     }
 
     @Test
-    public void testAudioOpBlockedOnSuspend() throws Exception {
+    public void testCameraBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_CAMERA);
+    }
+
+    @Test
+    public void testPlayAudioBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_PLAY_AUDIO);
+    }
+
+    @Test
+    public void testRecordAudioBlockedOnSuspend() throws Exception {
+        assertOpBlockedOnSuspend(OP_RECORD_AUDIO);
+    }
+
+    private void assertOpBlockedOnSuspend(int code) throws Exception {
         final IAppOpsService iAppOps = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
         final CountDownLatch latch = new CountDownLatch(1);
         final IAppOpsCallback watcher = new IAppOpsCallback.Stub() {
             @Override
             public void opChanged(int op, int uid, String packageName) {
-                if (op == OP_PLAY_AUDIO && packageName.equals(TEST_APP_PACKAGE_NAME)) {
+                if (op == code && packageName.equals(TEST_APP_PACKAGE_NAME)) {
                     latch.countDown();
                 }
             }
         };
-        iAppOps.startWatchingMode(OP_PLAY_AUDIO, TEST_APP_PACKAGE_NAME, watcher);
+        iAppOps.startWatchingMode(code, TEST_APP_PACKAGE_NAME, watcher);
         final int testPackageUid = mPackageManager.getPackageUid(TEST_APP_PACKAGE_NAME, 0);
-        int audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO,
-                AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME);
-        assertEquals("Audio muted for unsuspended package", MODE_ALLOWED, audioOpMode);
+        int opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
+        assertEquals("Op " + opToName(code) + " disallowed for unsuspended package", MODE_ALLOWED,
+                opMode);
         suspendTestPackage(null, null, null);
         assertTrue("AppOpsWatcher did not callback", latch.await(5, TimeUnit.SECONDS));
-        audioOpMode = iAppOps.checkAudioOperation(OP_PLAY_AUDIO,
-                AudioAttributes.USAGE_UNKNOWN, testPackageUid, TEST_APP_PACKAGE_NAME);
-        assertEquals("Audio not muted for suspended package", MODE_IGNORED, audioOpMode);
+        opMode = iAppOps.checkOperation(code, testPackageUid, TEST_APP_PACKAGE_NAME);
+        assertEquals("Op " + opToName(code) + " allowed for suspended package", MODE_IGNORED,
+                opMode);
         iAppOps.stopWatchingMode(watcher);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
new file mode 100644
index 0000000..7aa3d0d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.protolog.ProtoLogImpl.PROTOLOG_VERSION;
+
+import static org.junit.Assert.assertArrayEquals;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.protolog.common.IProtoLogGroup;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogImplTest {
+
+    private static final byte[] MAGIC_HEADER = new byte[]{
+            0x9, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47
+    };
+
+    private ProtoLogImpl mProtoLog;
+    private File mFile;
+
+    @Mock
+    private ProtoLogViewerConfigReader mReader;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        final Context testContext = getInstrumentation().getContext();
+        mFile = testContext.getFileStreamPath("tracing_test.dat");
+        //noinspection ResultOfMethodCallIgnored
+        mFile.delete();
+        mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader);
+    }
+
+    @After
+    public void tearDown() {
+        if (mFile != null) {
+            //noinspection ResultOfMethodCallIgnored
+            mFile.delete();
+        }
+        ProtoLogImpl.setSingleInstance(null);
+    }
+
+    @Test
+    public void isEnabled_returnsFalseByDefault() {
+        assertFalse(mProtoLog.isProtoEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsTrueAfterStart() {
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        assertTrue(mProtoLog.isProtoEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsFalseAfterStop() {
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+        assertFalse(mProtoLog.isProtoEnabled());
+    }
+
+    @Test
+    public void logFile_startsWithMagicHeader() throws Exception {
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+
+        assertTrue("Log file should exist", mFile.exists());
+
+        byte[] header = new byte[MAGIC_HEADER.length];
+        try (InputStream is = new FileInputStream(mFile)) {
+            assertEquals(MAGIC_HEADER.length, is.read(header));
+            assertArrayEquals(MAGIC_HEADER, header);
+        }
+    }
+
+    @Test
+    public void getSingleInstance() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        assertSame(mockedProtoLog, ProtoLogImpl.getSingleInstance());
+    }
+
+    @Test
+    public void d_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.DEBUG), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void v_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.VERBOSE), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void i_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.INFO), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void w_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
+                4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.WARN), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void e_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void wtf_logCalled() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
+                1234, 4321, "test %d");
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.WTF), eq(
+                TestProtoLogGroup.TEST_GROUP),
+                eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+    }
+
+    @Test
+    public void log_logcatEnabledExternalMessage() {
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+        implSpy.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+                new Object[]{true, 10000, 20000, 30000, 0.0001, 0.00002, "test", 0.000003});
+
+        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+                ProtoLogImpl.LogLevel.INFO),
+                eq("test true 10000 % 47040 7530 1.000000e-04 2.00000e-05 test 0.000003"));
+        verify(mReader).getViewerString(eq(1234));
+    }
+
+    @Test
+    public void log_logcatEnabledInvalidMessage() {
+        when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+        ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+        implSpy.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+                new Object[]{true, 10000, 0.0001, 0.00002, "test"});
+
+        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+                ProtoLogImpl.LogLevel.INFO),
+                eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
+        verify(mReader).getViewerString(eq(1234));
+    }
+
+    @Test
+    public void log_logcatEnabledInlineMessage() {
+        when(mReader.getViewerString(anyInt())).thenReturn("test %d");
+        ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+        implSpy.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+                new Object[]{5});
+
+        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+                ProtoLogImpl.LogLevel.INFO), eq("test 5"));
+        verify(mReader, never()).getViewerString(anyInt());
+    }
+
+    @Test
+    public void log_logcatEnabledNoMessage() {
+        when(mReader.getViewerString(anyInt())).thenReturn(null);
+        ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+        implSpy.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+                new Object[]{5});
+
+        verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+                ProtoLogImpl.LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
+        verify(mReader).getViewerString(eq(1234));
+    }
+
+    @Test
+    public void log_logcatDisabled() {
+        when(mReader.getViewerString(anyInt())).thenReturn("test %d");
+        ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+        implSpy.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+                new Object[]{5});
+
+        verify(implSpy, never()).passToLogcat(any(), any(), any());
+        verify(mReader, never()).getViewerString(anyInt());
+    }
+
+    private static class ProtoLogData {
+        Integer mMessageHash = null;
+        Long mElapsedTime = null;
+        LinkedList<String> mStrParams = new LinkedList<>();
+        LinkedList<Long> mSint64Params = new LinkedList<>();
+        LinkedList<Double> mDoubleParams = new LinkedList<>();
+        LinkedList<Boolean> mBooleanParams = new LinkedList<>();
+    }
+
+    private ProtoLogData readProtoLogSingle(ProtoInputStream ip) throws IOException {
+        while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            if (ip.getFieldNumber() == (int) ProtoLogFileProto.VERSION) {
+                assertEquals(PROTOLOG_VERSION, ip.readString(ProtoLogFileProto.VERSION));
+                continue;
+            }
+            if (ip.getFieldNumber() != (int) ProtoLogFileProto.LOG) {
+                continue;
+            }
+            long token = ip.start(ProtoLogFileProto.LOG);
+            ProtoLogData data = new ProtoLogData();
+            while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (ip.getFieldNumber()) {
+                    case (int) ProtoLogMessage.MESSAGE_HASH: {
+                        data.mMessageHash = ip.readInt(ProtoLogMessage.MESSAGE_HASH);
+                        break;
+                    }
+                    case (int) ProtoLogMessage.ELAPSED_REALTIME_NANOS: {
+                        data.mElapsedTime = ip.readLong(ProtoLogMessage.ELAPSED_REALTIME_NANOS);
+                        break;
+                    }
+                    case (int) ProtoLogMessage.STR_PARAMS: {
+                        data.mStrParams.add(ip.readString(ProtoLogMessage.STR_PARAMS));
+                        break;
+                    }
+                    case (int) ProtoLogMessage.SINT64_PARAMS: {
+                        data.mSint64Params.add(ip.readLong(ProtoLogMessage.SINT64_PARAMS));
+                        break;
+                    }
+                    case (int) ProtoLogMessage.DOUBLE_PARAMS: {
+                        data.mDoubleParams.add(ip.readDouble(ProtoLogMessage.DOUBLE_PARAMS));
+                        break;
+                    }
+                    case (int) ProtoLogMessage.BOOLEAN_PARAMS: {
+                        data.mBooleanParams.add(ip.readBoolean(ProtoLogMessage.BOOLEAN_PARAMS));
+                        break;
+                    }
+                }
+            }
+            ip.end(token);
+            return data;
+        }
+        return null;
+    }
+
+    @Test
+    public void log_protoEnabled() throws Exception {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        long before = SystemClock.elapsedRealtimeNanos();
+        mProtoLog.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+                0b1110101001010100, null,
+                new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+        long after = SystemClock.elapsedRealtimeNanos();
+        mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+        try (InputStream is = new FileInputStream(mFile)) {
+            ProtoInputStream ip = new ProtoInputStream(is);
+            ProtoLogData data = readProtoLogSingle(ip);
+            assertNotNull(data);
+            assertEquals(1234, data.mMessageHash.longValue());
+            assertTrue(before < data.mElapsedTime && data.mElapsedTime < after);
+            assertArrayEquals(new String[]{"test"}, data.mStrParams.toArray());
+            assertArrayEquals(new Long[]{1L, 2L, 3L}, data.mSint64Params.toArray());
+            assertArrayEquals(new Double[]{0.4, 0.5, 0.6}, data.mDoubleParams.toArray());
+            assertArrayEquals(new Boolean[]{true}, data.mBooleanParams.toArray());
+        }
+    }
+
+    @Test
+    public void log_invalidParamsMask() throws Exception {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        long before = SystemClock.elapsedRealtimeNanos();
+        mProtoLog.log(
+                ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+                0b01100100, null,
+                new Object[]{"test", 1, 0.1, true});
+        long after = SystemClock.elapsedRealtimeNanos();
+        mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+        try (InputStream is = new FileInputStream(mFile)) {
+            ProtoInputStream ip = new ProtoInputStream(is);
+            ProtoLogData data = readProtoLogSingle(ip);
+            assertNotNull(data);
+            assertEquals(1234, data.mMessageHash.longValue());
+            assertTrue(before < data.mElapsedTime && data.mElapsedTime < after);
+            assertArrayEquals(new String[]{"test", "(INVALID PARAMS_MASK) true"},
+                    data.mStrParams.toArray());
+            assertArrayEquals(new Long[]{1L}, data.mSint64Params.toArray());
+            assertArrayEquals(new Double[]{0.1}, data.mDoubleParams.toArray());
+            assertArrayEquals(new Boolean[]{}, data.mBooleanParams.toArray());
+        }
+    }
+
+    @Test
+    public void log_protoDisabled() throws Exception {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+        mProtoLog.startProtoLog(mock(PrintWriter.class));
+        mProtoLog.log(ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+                0b11, null, new Object[]{true});
+        mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+        try (InputStream is = new FileInputStream(mFile)) {
+            ProtoInputStream ip = new ProtoInputStream(is);
+            ProtoLogData data = readProtoLogSingle(ip);
+            assertNull(data);
+        }
+    }
+
+    private enum TestProtoLogGroup implements IProtoLogGroup {
+        TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+        private final boolean mEnabled;
+        private volatile boolean mLogToProto;
+        private volatile boolean mLogToLogcat;
+        private final String mTag;
+
+        /**
+         * @param enabled     set to false to exclude all log statements for this group from
+         *                    compilation,
+         *                    they will not be available in runtime.
+         * @param logToProto  enable binary logging for the group
+         * @param logToLogcat enable text logging for the group
+         * @param tag         name of the source of the logged message
+         */
+        TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+            this.mEnabled = enabled;
+            this.mLogToProto = logToProto;
+            this.mLogToLogcat = logToLogcat;
+            this.mTag = tag;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        @Override
+        public boolean isLogToProto() {
+            return mLogToProto;
+        }
+
+        @Override
+        public boolean isLogToLogcat() {
+            return mLogToLogcat;
+        }
+
+        @Override
+        public boolean isLogToAny() {
+            return mLogToLogcat || mLogToProto;
+        }
+
+        @Override
+        public String getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setLogToProto(boolean logToProto) {
+            this.mLogToProto = logToProto;
+        }
+
+        @Override
+        public void setLogToLogcat(boolean logToLogcat) {
+            this.mLogToLogcat = logToLogcat;
+        }
+
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..0254055
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogViewerConfigReaderTest {
+    private static final String TEST_VIEWER_CONFIG = "{\n"
+            + "  \"version\": \"1.0.0\",\n"
+            + "  \"messages\": {\n"
+            + "    \"70933285\": {\n"
+            + "      \"message\": \"Test completed successfully: %b\",\n"
+            + "      \"level\": \"ERROR\",\n"
+            + "      \"group\": \"GENERIC_WM\"\n"
+            + "    },\n"
+            + "    \"1792430067\": {\n"
+            + "      \"message\": \"Attempted to add window to a display that does not exist: %d."
+            + "  Aborting.\",\n"
+            + "      \"level\": \"WARN\",\n"
+            + "      \"group\": \"GENERIC_WM\"\n"
+            + "    },\n"
+            + "    \"1352021864\": {\n"
+            + "      \"message\": \"Test 2\",\n"
+            + "      \"level\": \"WARN\",\n"
+            + "      \"group\": \"GENERIC_WM\"\n"
+            + "    },\n"
+            + "    \"409412266\": {\n"
+            + "      \"message\": \"Window %s is already added\",\n"
+            + "      \"level\": \"WARN\",\n"
+            + "      \"group\": \"GENERIC_WM\"\n"
+            + "    }\n"
+            + "  },\n"
+            + "  \"groups\": {\n"
+            + "    \"GENERIC_WM\": {\n"
+            + "      \"tag\": \"WindowManager\"\n"
+            + "    }\n"
+            + "  }\n"
+            + "}\n";
+
+
+    private ProtoLogViewerConfigReader
+            mConfig = new ProtoLogViewerConfigReader();
+    private File mTestViewerConfig;
+
+    @Before
+    public void setUp() throws IOException {
+        mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+        OutputStreamWriter writer = new OutputStreamWriter(
+                new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+        writer.write(TEST_VIEWER_CONFIG);
+        writer.close();
+    }
+
+    @After
+    public void tearDown() {
+        //noinspection ResultOfMethodCallIgnored
+        mTestViewerConfig.delete();
+    }
+
+    @Test
+    public void getViewerString_notLoaded() {
+        assertNull(mConfig.getViewerString(1));
+    }
+
+    @Test
+    public void loadViewerConfig() {
+        mConfig.loadViewerConfig(null, mTestViewerConfig.getAbsolutePath());
+        assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+        assertEquals("Test 2", mConfig.getViewerString(1352021864));
+        assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+        assertNull(mConfig.getViewerString(1));
+    }
+
+    @Test
+    public void loadViewerConfig_invalidFile() {
+        mConfig.loadViewerConfig(null, "/tmp/unknown/file/does/not/exist");
+        // No exception is thrown.
+        assertNull(mConfig.getViewerString(1));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java b/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
new file mode 100644
index 0000000..4c7f5fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.protolog.common;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LogDataTypeTest {
+    @Test
+    public void parseFormatString() {
+        String str = "%b %d %o %x %f %e %g %s %%";
+        List<Integer> out = LogDataType.parseFormatString(str);
+        assertEquals(Arrays.asList(
+                LogDataType.BOOLEAN,
+                LogDataType.LONG,
+                LogDataType.LONG,
+                LogDataType.LONG,
+                LogDataType.DOUBLE,
+                LogDataType.DOUBLE,
+                LogDataType.DOUBLE,
+                LogDataType.STRING
+        ), out);
+    }
+
+    @Test(expected = InvalidFormatStringException.class)
+    public void parseFormatString_invalid() {
+        String str = "%q";
+        LogDataType.parseFormatString(str);
+    }
+
+    @Test
+    public void logDataTypesToBitMask() {
+        List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+                LogDataType.LONG, LogDataType.BOOLEAN);
+        int mask = LogDataType.logDataTypesToBitMask(types);
+        assertEquals(0b11011000, mask);
+    }
+
+    @Test(expected = BitmaskConversionException.class)
+    public void logDataTypesToBitMask_toManyParams() {
+        ArrayList<Integer> types = new ArrayList<>();
+        for (int i = 0; i <= 16; i++) {
+            types.add(LogDataType.STRING);
+        }
+        LogDataType.logDataTypesToBitMask(types);
+    }
+
+    @Test
+    public void bitmaskToLogDataTypes() {
+        int bitmask = 0b11011000;
+        List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+                LogDataType.LONG, LogDataType.BOOLEAN);
+        for (int i = 0; i < types.size(); i++) {
+            assertEquals(types.get(i).intValue(), LogDataType.bitmaskToLogDataType(bitmask, i));
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index 8cb5197..0b8c2a55 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -45,8 +45,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Set;
 
 @RunWith(JUnit4.class)
 public class AppDataRollbackHelperTest {
@@ -250,28 +248,22 @@
         dataForRestore.info.getPackages().add(pendingRestore);
         dataForRestore.info.getPackages().add(wasRecentlyRestored);
 
-        Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
-                Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
-                    dataForRestore));
         InOrder inOrder = Mockito.inOrder(installer);
 
         // Check that pending backup and restore for the same package mutually destroyed each other.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
         assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
         assertNull(wasRecentlyRestored.getRestoreInfo(37));
 
         // Check that backup was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
         inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
                 eq(Installer.FLAG_STORAGE_CE));
         assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
         assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));
 
-        // Check that changed returns correct Rollback.
-        assertEquals(3, changed.size());
-        assertTrue(changed.contains(dataWithPendingBackup));
-        assertTrue(changed.contains(dataWithRecentRestore));
-        assertTrue(changed.contains(dataForRestore));
-
         // Check that restore was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
         inOrder.verify(installer).restoreAppDataSnapshot(
                 eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
                 eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
new file mode 100644
index 0000000..b5925a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.util.IntArray;
+import android.util.SparseLongArray;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class RollbackUnitTest {
+
+    @Test
+    public void newEmptyStagedRollbackDefaults() {
+        int rollbackId = 123;
+        int sessionId = 567;
+        File file = new File("/test/testing");
+
+        Rollback rollback = new Rollback(rollbackId, file, sessionId);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+        assertThat(rollback.isStaged()).isTrue();
+        assertThat(rollback.getStagedSessionId()).isEqualTo(567);
+    }
+
+    @Test
+    public void newEmptyNonStagedRollbackDefaults() {
+        int rollbackId = 123;
+        File file = new File("/test/testing");
+
+        Rollback rollback = new Rollback(rollbackId, file, -1);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+        assertThat(rollback.isStaged()).isFalse();
+    }
+
+    @Test
+    public void rollbackStateChanges() {
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.isAvailable()).isFalse();
+        assertThat(rollback.isCommitted()).isFalse();
+
+        rollback.setAvailable();
+
+        assertThat(rollback.isEnabling()).isFalse();
+        assertThat(rollback.isAvailable()).isTrue();
+        assertThat(rollback.isCommitted()).isFalse();
+
+        rollback.setCommitted();
+
+        assertThat(rollback.isEnabling()).isFalse();
+        assertThat(rollback.isAvailable()).isFalse();
+        assertThat(rollback.isCommitted()).isTrue();
+    }
+
+    @Test
+    public void getPackageNamesAllAndJustApex() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 12, 10, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 12, 10, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 12, 10, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.getPackageNames()).containsExactly(pkg1, pkg2, pkg3, pkg4);
+        assertThat(rollback.getApexPackageNames()).containsExactly(pkg2, pkg4);
+    }
+
+    @Test
+    public void includesPackages() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 18, 12, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 157, 156, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 99, 1, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.includesPackage(pkg2)).isTrue();
+        assertThat(rollback.includesPackage(pkg3)).isTrue();
+        assertThat(rollback.includesPackage("com.something.else")).isFalse();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 12)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 1)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 18)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 12)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 157)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 156)).isTrue();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 15)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 99)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 100)).isTrue();
+    }
+
+    private static PackageRollbackInfo pkgInfoFor(
+            String packageName, long fromVersion, long toVersion, boolean isApex) {
+        return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
+                new VersionedPackage(packageName, toVersion),
+                new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
new file mode 100644
index 0000000..8cbf8e5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import static com.android.server.stats.IonMemoryUtil.parseIonHeapSizeFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:IonMemoryUtilTest
+ */
+@SmallTest
+public class IonMemoryUtilTest {
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join(
+            "\n",
+            "          client              pid             size",
+            "----------------------------------------------------",
+            " audio@2.0-servi              765             4096",
+            " audio@2.0-servi              765            61440",
+            " audio@2.0-servi              765             4096",
+            "     voip_client               96             8192",
+            "     voip_client               96             4096",
+            "   system_server             1232         16728064",
+            "  surfaceflinger              611         50642944",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total          55193600",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join(
+            "\n",
+            "          client              pid             size      page counts"
+                    + "--------------------------------------------------       4K       8K      "
+                    + "16K      32K      64K     128K     256K     512K       1M       2M       "
+                    + "4M      >=8M",
+            "   system_server             1705         58097664    13120      532        "
+                    + "0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851            16384        0        2        0        "
+                    + "0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851             4096        1        0        0       "
+                    + " 0        0        0        0        0        0        0        0        "
+                    + "0M",
+            " audio@2.0-servi              851             4096        1        0      "
+                    + "  0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total         159928320",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_emptyContents() {
+        assertThat(parseIonHeapSizeFromDebugfs("")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_invalidValue() {
+        assertThat(parseIonHeapSizeFromDebugfs("<<no-value>>")).isEqualTo(0);
+
+        assertThat(parseIonHeapSizeFromDebugfs("\ntotal 12345678901234567890\n")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_correctValue() {
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .isEqualTo(55193600);
+
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .isEqualTo(159928320);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("")).hasSize(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("<<no-value>>").size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .containsExactly(
+                        createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440),
+                        createIonAllocations(96, 8192 + 4096, 2, 8192),
+                        createIonAllocations(1232, 16728064, 1, 16728064),
+                        createIonAllocations(611, 50642944, 1, 50642944));
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .containsExactly(
+                        createIonAllocations(1705, 58097664, 1, 58097664),
+                        createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384));
+    }
+
+    private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count,
+            long maxSizeInBytes) {
+        IonAllocations allocations = new IonAllocations();
+        allocations.pid = pid;
+        allocations.totalSizeInBytes = totalSizeInBytes;
+        allocations.count = count;
+        allocations.maxSizeInBytes = maxSizeInBytes;
+        return allocations;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
new file mode 100644
index 0000000..ae57774
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import static com.android.server.stats.ProcfsMemoryUtil.parseMemorySnapshotFromStatus;
+import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ProcfsMemoryUtilTest
+ */
+@SmallTest
+public class ProcfsMemoryUtilTest {
+    private static final String STATUS_CONTENTS = "Name:\tandroid.youtube\n"
+            + "State:\tS (sleeping)\n"
+            + "Tgid:\t12088\n"
+            + "Pid:\t12088\n"
+            + "PPid:\t723\n"
+            + "TracerPid:\t0\n"
+            + "Uid:\t10083\t10083\t10083\t10083\n"
+            + "Gid:\t10083\t10083\t10083\t10083\n"
+            + "Ngid:\t0\n"
+            + "FDSize:\t128\n"
+            + "Groups:\t3003 9997 20083 50083 \n"
+            + "VmPeak:\t 4546844 kB\n"
+            + "VmSize:\t 4542636 kB\n"
+            + "VmLck:\t       0 kB\n"
+            + "VmPin:\t       0 kB\n"
+            + "VmHWM:\t  137668 kB\n" // RSS high-water mark
+            + "VmRSS:\t  126776 kB\n" // RSS
+            + "RssAnon:\t   37860 kB\n"
+            + "RssFile:\t   88764 kB\n"
+            + "RssShmem:\t     152 kB\n"
+            + "VmData:\t 4125112 kB\n"
+            + "VmStk:\t    8192 kB\n"
+            + "VmExe:\t      24 kB\n"
+            + "VmLib:\t  102432 kB\n"
+            + "VmPTE:\t    1300 kB\n"
+            + "VmPMD:\t      36 kB\n"
+            + "VmSwap:\t      22 kB\n" // Swap
+            + "Threads:\t95\n"
+            + "SigQ:\t0/13641\n"
+            + "SigPnd:\t0000000000000000\n"
+            + "ShdPnd:\t0000000000000000\n"
+            + "SigBlk:\t0000000000001204\n"
+            + "SigIgn:\t0000000000000001\n"
+            + "SigCgt:\t00000006400084f8\n"
+            + "CapInh:\t0000000000000000\n"
+            + "CapPrm:\t0000000000000000\n"
+            + "CapEff:\t0000000000000000\n"
+            + "CapBnd:\t0000000000000000\n"
+            + "CapAmb:\t0000000000000000\n"
+            + "Seccomp:\t2\n"
+            + "Cpus_allowed:\tff\n"
+            + "Cpus_allowed_list:\t0-7\n"
+            + "Mems_allowed:\t1\n"
+            + "Mems_allowed_list:\t0\n"
+            + "voluntary_ctxt_switches:\t903\n"
+            + "nonvoluntary_ctxt_switches:\t104\n";
+
+    @Test
+    public void testParseVmHWMFromStatus_parsesCorrectValue() {
+        assertThat(parseVmHWMFromStatus(STATUS_CONTENTS)).isEqualTo(137668);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_invalidValue() {
+        assertThat(parseVmHWMFromStatus("test\nVmHWM: x0x0x\ntest")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_emptyContents() {
+        assertThat(parseVmHWMFromStatus("")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_parsesCorrectValue() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus(STATUS_CONTENTS);
+        assertThat(snapshot.rssInKilobytes).isEqualTo(126776);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(37860);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(22);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_invalidValue() {
+        MemorySnapshot snapshot =
+                parseMemorySnapshotFromStatus("test\nVmRSS:\tx0x0x\nVmSwap:\t1 kB\ntest");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(1);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_emptyContents() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus("");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(0);
+        assertThat(snapshot.isEmpty()).isTrue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java
new file mode 100644
index 0000000..09b75e7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+
+/**
+ * Test class for {@link TraceBuffer}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TraceBufferTest
+ */
+@SmallTest
+@Presubmit
+public class TraceBufferTest {
+    private File mFile;
+    private TraceBuffer mBuffer;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context testContext = getInstrumentation().getContext();
+        mFile = testContext.getFileStreamPath("tracing_test.dat");
+        mFile.delete();
+
+        mBuffer = new TraceBuffer(10);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mFile.delete();
+    }
+
+    @Test
+    public void test_addItem() {
+        ProtoOutputStream toWrite = getDummy(1);
+        final int objectSize = toWrite.getRawSize();
+        mBuffer.setCapacity(objectSize);
+        mBuffer.resetBuffer();
+
+        Preconditions.checkArgument(mBuffer.size() == 0);
+
+        mBuffer.add(toWrite);
+
+        assertEquals("Item was not added to the buffer", 1, mBuffer.size());
+        assertEquals("Total buffer getSize differs from inserted object",
+                mBuffer.getBufferSize(), objectSize);
+        assertEquals("Available buffer space does not match used one", 0,
+                mBuffer.getAvailableSpace());
+    }
+
+    @Test
+    public void test_addItemMustOverwriteOne() {
+        ProtoOutputStream toWrite1 = getDummy(1);
+        ProtoOutputStream toWrite2 = getDummy(2);
+        ProtoOutputStream toWrite3 = getDummy(3);
+        final int objectSize = toWrite1.getRawSize();
+        final int bufferCapacity = objectSize * 2 + 1;
+        mBuffer.setCapacity(bufferCapacity);
+        mBuffer.resetBuffer();
+
+        mBuffer.add(toWrite1);
+        byte[] toWrite1Bytes = toWrite1.getBytes();
+        assertTrue("First element should be in the list",
+                mBuffer.contains(toWrite1Bytes));
+
+        mBuffer.add(toWrite2);
+        byte[] toWrite2Bytes = toWrite2.getBytes();
+        assertTrue("First element should be in the list",
+                mBuffer.contains(toWrite1Bytes));
+        assertTrue("Second element should be in the list",
+                mBuffer.contains(toWrite2Bytes));
+
+        mBuffer.add(toWrite3);
+        byte[] toWrite3Bytes = toWrite3.getBytes();
+        assertTrue("First element should not be in the list",
+                !mBuffer.contains(toWrite1Bytes));
+        assertTrue("Second element should be in the list",
+                mBuffer.contains(toWrite2Bytes));
+        assertTrue("Third element should be in the list",
+                mBuffer.contains(toWrite3Bytes));
+        assertEquals("Buffer should have 2 elements", 2, mBuffer.size());
+        assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
+                mBuffer.getBufferSize(), bufferCapacity - 1);
+        assertEquals(" Buffer is full, available space should be 0", 1,
+                mBuffer.getAvailableSpace());
+    }
+
+    @Test
+    public void test_addItemMustOverwriteMultiple() {
+        ProtoOutputStream toWriteSmall1 = getDummy(1);
+        ProtoOutputStream toWriteSmall2 = getDummy(2);
+        final int objectSize = toWriteSmall1.getRawSize();
+        final int bufferCapacity = objectSize * 2;
+        mBuffer.setCapacity(bufferCapacity);
+        mBuffer.resetBuffer();
+
+        ProtoOutputStream toWriteBig = new ProtoOutputStream();
+        toWriteBig.write(MAGIC_NUMBER, 1);
+        toWriteBig.write(MAGIC_NUMBER, 2);
+
+        mBuffer.add(toWriteSmall1);
+        byte[] toWriteSmall1Bytes = toWriteSmall1.getBytes();
+        assertTrue("First element should be in the list",
+                mBuffer.contains(toWriteSmall1Bytes));
+
+        mBuffer.add(toWriteSmall2);
+        byte[] toWriteSmall2Bytes = toWriteSmall2.getBytes();
+        assertTrue("First element should be in the list",
+                mBuffer.contains(toWriteSmall1Bytes));
+        assertTrue("Second element should be in the list",
+                mBuffer.contains(toWriteSmall2Bytes));
+
+        mBuffer.add(toWriteBig);
+        byte[] toWriteBigBytes = toWriteBig.getBytes();
+        assertTrue("Third element should overwrite all others",
+                !mBuffer.contains(toWriteSmall1Bytes));
+        assertTrue("Third element should overwrite all others",
+                !mBuffer.contains(toWriteSmall2Bytes));
+        assertTrue("Third element should overwrite all others",
+                mBuffer.contains(toWriteBigBytes));
+
+        assertEquals(" Buffer should have only 1 big element", 1, mBuffer.size());
+        assertEquals(String.format(" Buffer is full, used space should be %d", bufferCapacity),
+                mBuffer.getBufferSize(), bufferCapacity);
+        assertEquals(" Buffer is full, available space should be 0", 0,
+                mBuffer.getAvailableSpace());
+    }
+
+    @Test
+    public void test_startResetsBuffer() {
+        ProtoOutputStream toWrite = getDummy(1);
+        mBuffer.resetBuffer();
+        Preconditions.checkArgument(mBuffer.size() == 0);
+
+        mBuffer.add(toWrite);
+        assertEquals("Item was not added to the buffer", 1, mBuffer.size());
+        mBuffer.resetBuffer();
+        assertEquals("Buffer should be empty after reset", 0, mBuffer.size());
+        assertEquals("Buffer size should be 0 after reset", 0, mBuffer.getBufferSize());
+    }
+
+    private ProtoOutputStream getDummy(int value) {
+        ProtoOutputStream toWrite = new ProtoOutputStream();
+        toWrite.write(MAGIC_NUMBER, value);
+        toWrite.flush();
+
+        return toWrite;
+    }
+
+}
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 7453c48..180deb5 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 14b71ec..3c2d550 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -27,9 +27,11 @@
 
 import com.android.server.uri.UriGrantsManagerInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 public class UiServiceTestCase {
@@ -77,4 +79,9 @@
         when(mUgmInternal.checkGrantUriPermission(
                 anyInt(), anyString(), any(Uri.class), anyInt(), anyInt())).thenReturn(-1);
     }
+
+    @After
+    public final void cleanUpMockito() {
+        Mockito.framework().clearInlineMocks();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 273a9e6..7459c4b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -86,7 +86,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -100,7 +100,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
@@ -114,7 +114,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
 
@@ -128,7 +128,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -142,7 +142,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(false);
+        when(mConfig.bubblesEnabled()).thenReturn(false);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 8aaf29a..7b7470c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -58,13 +57,13 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.server.UiServiceTestCase;
 
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -76,6 +75,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -331,6 +331,147 @@
         }
     }
 
+    /** Test that restore ignores the user id attribute and applies the data to the target user. */
+    @Test
+    public void testWriteReadXml_writeReadDefaults() throws Exception {
+        // setup
+        ManagedServices service1 =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        ManagedServices service2 =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        BufferedOutputStream outStream = new BufferedOutputStream(baos);
+        serializer.setOutput(outStream, "utf-8");
+
+        //data setup
+        service1.addDefaultComponentOrPackage("package/class");
+        serializer.startDocument(null, true);
+        service1.writeXml(serializer, false, 0);
+        serializer.endDocument();
+        outStream.flush();
+
+        final XmlPullParser parser = Xml.newPullParser();
+        BufferedInputStream input = new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray()));
+
+        parser.setInput(input, StandardCharsets.UTF_8.name());
+        XmlUtils.beginDocument(parser, "test");
+        service2.readXml(parser, null, false, 0);
+        ArraySet<ComponentName> defaults = service2.getDefaultComponents();
+
+        assertEquals(1, defaults.size());
+        assertEquals(new ComponentName("package", "class"), defaults.valueAt(0));
+
+    }
+
+    @Test
+    public void resetPackage_enableDefaultsOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> componentsToActivate =
+                service.resetComponents("package", 0);
+
+        assertEquals(1, componentsToActivate.get(true).size());
+        assertEquals(new ComponentName("package", "default"),
+                componentsToActivate.get(true).get(0));
+    }
+
+
+    @Test
+    public void resetPackage_nonDefaultsRemoved() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> componentsToActivate =
+                service.resetComponents("package", 0);
+
+        assertEquals(1, componentsToActivate.get(true).size());
+        assertEquals(new ComponentName("package", "not-default"),
+                componentsToActivate.get(false).get(0));
+    }
+
+    @Test
+    public void resetPackage_onlyDefaultsOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        assertEquals(3, service.getAllowedComponents(0).size());
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(0);
+        assertEquals(3, components.size());
+        assertTrue(components.contains(new ComponentName("package", "default")));
+    }
+
+    @Test
+    public void resetPackage_affectCurrentUserOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                1, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(1);
+        assertEquals(3, components.size());
+    }
+
+    @Test
+    public void resetPackage_samePackageMultipleClasses() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addApprovedList(
+                "package/class:another-package/class:package2/class",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(0);
+        assertEquals(5, components.size());
+        assertTrue(components.contains(new ComponentName("package", "default")));
+    }
+
     /** Test that backup only writes packages/components that belong to the target user. */
     @Test
     public void testWriteXml_onlyBackupsForTargetUser() throws Exception {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index e15af3d..0b4760d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -68,6 +68,7 @@
     private final int uid2 = 1111111;
     private static final String TEST_CHANNEL_ID = "test_channel_id";
 
+    private NotificationRecord mRecordMinCallNonInterruptive;
     private NotificationRecord mRecordMinCall;
     private NotificationRecord mRecordHighCall;
     private NotificationRecord mRecordDefaultMedia;
@@ -105,6 +106,18 @@
         smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
                 Settings.Secure.SMS_DEFAULT_APPLICATION);
 
+        Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setCategory(Notification.CATEGORY_CALL)
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
+                new StatusBarNotification(callPkg,
+                        callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
+                        nonInterruptiveNotif,
+                        new UserHandle(userId), "", 2000), getDefaultChannel());
+        mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+        mRecordMinCallNonInterruptive.setInterruptive(false);
+
         Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setCategory(Notification.CATEGORY_CALL)
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
@@ -113,6 +126,7 @@
                 callPkg, 1, "minCall", callUid, callUid, n1,
                 new UserHandle(userId), "", 2000), getDefaultChannel());
         mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+        mRecordMinCall.setInterruptive(true);
 
         Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setCategory(Notification.CATEGORY_CALL)
@@ -245,6 +259,7 @@
         expected.add(mRecordCheater);
         expected.add(mRecordCheaterColorized);
         expected.add(mRecordMinCall);
+        expected.add(mRecordMinCallNonInterruptive);
 
         List<NotificationRecord> actual = new ArrayList<>();
         actual.addAll(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 397d215..a9fe1a6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -51,6 +51,8 @@
 import android.service.notification.SnoozeCriterion;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.server.UiServiceTestCase;
 
 import org.junit.After;
@@ -61,8 +63,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.test.runner.AndroidJUnit4;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class NotificationListenerServiceTest extends UiServiceTestCase {
@@ -116,6 +116,7 @@
             assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
             assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
             assertEquals(canBubble(i), ranking.canBubble());
+            assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
         }
     }
 
@@ -182,7 +183,8 @@
                 tweak.isNoisy(),
                 (ArrayList) tweak.getSmartActions(),
                 (ArrayList) tweak.getSmartReplies(),
-                tweak.canBubble()
+                tweak.canBubble(),
+                tweak.visuallyInterruptive()
         );
         assertNotEquals(nru, nru2);
     }
@@ -258,7 +260,8 @@
                     getNoisy(i),
                     getSmartActions(key, i),
                     getSmartReplies(key, i),
-                    canBubble(i)
+                    canBubble(i),
+                    visuallyInterruptive(i)
             );
             rankings[i] = ranking;
         }
@@ -363,6 +366,10 @@
         return index % 4 == 0;
     }
 
+    private boolean visuallyInterruptive(int index) {
+        return index % 4 == 0;
+    }
+
     private void assertActionsEqual(
             List<Notification.Action> expecteds, List<Notification.Action> actuals) {
         assertEquals(expecteds.size(), actuals.size());
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 3ac7a79..2de8d05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
@@ -61,6 +62,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -208,9 +210,10 @@
     private AudioManager mAudioManager;
     @Mock
     ActivityManager mActivityManager;
-    NotificationManagerService.WorkerHandler mHandler;
     @Mock
     Resources mResources;
+    @Mock
+    RankingHandler mRankingHandler;
 
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
@@ -341,7 +344,6 @@
 
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
-        mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
         mContext.setMockPackageManager(mPackageManagerClient);
 
@@ -377,6 +379,15 @@
         // Setup managed services
         mListener = mListeners.new ManagedServiceInfo(
                 null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+        ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
+        ArraySet<ComponentName> components = new ArraySet<>();
+        components.add(defaultComponent);
+        when(mListeners.getDefaultComponents()).thenReturn(components);
+        when(mConditionProviders.getDefaultPackages())
+                .thenReturn(new ArraySet<>(Arrays.asList("config")));
+        when(mAssistants.getDefaultComponents()).thenReturn(components);
+        when(mAssistants.queryPackageForServices(
+                anyString(), anyInt(), anyInt())).thenReturn(components);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
         ManagedServices.Config listenerConfig = new ManagedServices.Config();
         listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS;
@@ -390,7 +401,7 @@
 
         when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
 
-        mService.init(mTestableLooper.getLooper(),
+        mService.init(mTestableLooper.getLooper(), mRankingHandler,
                 mPackageManager, mPackageManagerClient, mockLightsManager,
                 mListeners, mAssistants, mConditionProviders,
                 mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
@@ -409,6 +420,7 @@
                 PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
         assertNotNull(mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
+        clearInvocations(mRankingHandler);
     }
 
     @After
@@ -419,6 +431,12 @@
                 .getUiAutomation().dropShellPermissionIdentity();
     }
 
+    private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
+        changed.put(true, new ArrayList<>());
+        changed.put(false, new ArrayList<>());
+        return changed;
+    }
     private ApplicationInfo getApplicationInfo(String pkg, int uid) {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = uid;
@@ -446,7 +464,7 @@
     private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
             boolean channelEnabled) {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.bubblesEnabled(any())).thenReturn(globalEnabled);
+        when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
@@ -460,20 +478,31 @@
         Notification.Builder nb = new Notification.Builder(mContext, "a")
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid, "tag", uid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid,
+                "tag" + System.currentTimeMillis(), uid, 0,
                 nb.build(), new UserHandle(userId), null, postTime);
         return sbn;
     }
 
+
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String groupKey, boolean isSummary, boolean isBubble) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
+        if (isBubble) {
+            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
+        }
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
+                "tag" + System.currentTimeMillis(), mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -501,7 +530,7 @@
         if (isBubble) {
             nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
         }
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -568,6 +597,52 @@
                 .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
     }
 
+    private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
+            throws RemoteException {
+
+        // Notification that has bubble metadata
+        NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
+                "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+
+        // Make the package foreground so that we're allowed to be a bubble
+        when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
+                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+        waitForIdle();
+
+        // Make sure we are a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
+
+        // Plain notification without bubble metadata
+        NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
+                "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
+                nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(2, notifsAfter.length);
+
+        // Summary notification for both of those
+        NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
+                "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+        if (summaryAutoCancel) {
+            nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
+        }
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.sbn.getTag(),
+                nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(3, notifsAfter.length);
+
+        return nrSummary;
+    }
+
     @Test
     public void testCreateNotificationChannels_SingleChannel() throws Exception {
         final NotificationChannel channel =
@@ -702,7 +777,8 @@
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(channel)));
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testBlockedNotifications_blockedChannel",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -720,7 +796,7 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(1, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -749,7 +825,7 @@
 
         StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The first time a foreground service notification is shown, we allow the channel
@@ -771,7 +847,8 @@
 
         sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_userBlockedChannelForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The second time it is shown, we keep the user's preference.
@@ -785,7 +862,8 @@
     public void testBlockedNotifications_blockedChannelGroup() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+        when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).
+                thenReturn(true);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
@@ -802,7 +880,8 @@
         mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_blockedApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -816,7 +895,8 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_blockedAppForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -838,7 +918,8 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testEnqueuedRestrictedNotifications_asSystem",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -862,7 +943,8 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testEnqueuedRestrictedNotifications_notAutomotive",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -885,7 +967,8 @@
             final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
             sbn.getNotification().category = category;
             try {
-                mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                        "testEnqueuedRestrictedNotifications_badUser",
                         sbn.getId(), sbn.getNotification(), sbn.getUserId());
                 fail("Calls from non system apps should not allow use of restricted categories");
             } catch (SecurityException e) {
@@ -922,7 +1005,8 @@
 
     @Test
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -932,9 +1016,11 @@
 
     @Test
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationImmediatelyAfterEnqueue", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testCancelNotificationImmediatelyAfterEnqueue", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -944,12 +1030,15 @@
 
     @Test
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -964,7 +1053,8 @@
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         NotificationRecord r = generateNotificationRecord(null);
         final StatusBarNotification sbn = r.sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationsFromListenerImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
         waitForIdle();
@@ -977,7 +1067,8 @@
     @Test
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -988,11 +1079,26 @@
     }
 
     @Test
+    public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
+            throws Exception {
+        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        sbn.getNotification().flags =
+                Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        waitForIdle();
+        verify(mListeners, times(1)).notifyPostedLocked(any(), any());
+        verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), any());
+    }
+
+    @Test
     public void testUserInitiatedClearAll_noLeak() throws Exception {
         final NotificationRecord n = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testUserInitiatedClearAll_noLeak",
                 n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
         waitForIdle();
 
@@ -1015,9 +1121,11 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group1", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsCancelsChildren",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsCancelsChildren",
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -1030,7 +1138,8 @@
     public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testCancelAllNotificationsMultipleEnqueuedDoesNotCrash",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
@@ -1049,17 +1158,20 @@
                 mTestNotificationChannel, 2, "group1", false);
 
         // fully post parent notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
         // enqueue the child several times
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                     child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         }
         // make the parent a child, which will cancel the child notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                 parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
                 parentAsChild.sbn.getUserId());
         waitForIdle();
@@ -1071,7 +1183,8 @@
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1085,7 +1198,8 @@
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreOtherPackages",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
         waitForIdle();
@@ -1098,7 +1212,8 @@
     @Test
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_NullPkgRemovesAll",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
         waitForIdle();
@@ -1111,7 +1226,8 @@
     @Test
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_NullPkgIgnoresUserAllNotifications",
                 sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL);
         // Null pkg is how we signal a user switch.
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -1126,7 +1242,8 @@
     public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppInitiatedCancelAllNotifications_CancelsNoClearFlag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1211,7 +1328,12 @@
 
     @Test
     public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        Notification n =
+                new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                        .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                        .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0,
+                n, new UserHandle(mUid), null, 0);
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1228,12 +1350,13 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags =
                 Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
+                sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
         assertEquals(0, mService.getNotificationRecordCount());
@@ -1321,21 +1444,22 @@
         // should not be returned
         final NotificationRecord group2 = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group2", true);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
                 group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId());
         waitForIdle();
 
         // should not be returned
         final NotificationRecord nonGroup = generateNotificationRecord(
                 mTestNotificationChannel, 3, null, false);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
                 nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId());
         waitForIdle();
 
         // same group, child, should be returned
         final NotificationRecord group1Child = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group1", false);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null, group1Child.sbn.getId(),
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
+                group1Child.sbn.getId(),
                 group1Child.sbn.getNotification(), group1Child.sbn.getUserId());
         waitForIdle();
 
@@ -1392,7 +1516,8 @@
     public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1509,7 +1634,7 @@
                         new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
@@ -1524,8 +1649,8 @@
                 mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
-                generateNotificationRecord(null, tv).getNotification(), 0);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
+                0, generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
     }
@@ -2165,7 +2290,7 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -2178,7 +2303,7 @@
         final NotificationRecord record = generateNotificationRecord(
                 mTestNotificationChannel, 2, null, false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
                 record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId());
         waitForIdle();
 
@@ -2190,7 +2315,7 @@
         final NotificationRecord parent = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostGroupSummary_noUnsnoozing",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
@@ -2604,31 +2729,37 @@
                 .setColorized(true)
                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testNoFakeColorizedPermission", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         NotificationRecord posted = mService.findNotificationLocked(
-                PKG, null, nr.sbn.getId(), nr.sbn.getUserId());
+                PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getUserId());
 
         assertFalse(posted.getNotification().isColorized());
     }
 
     @Test
-    public void testGetNotificationCountLocked() throws Exception {
+    public void testGetNotificationCountLocked() {
+        String sampleTagToExclude = null;
+        int sampleIdToExclude = 0;
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
             mService.addEnqueuedNotification(r);
+
         }
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
             mService.addNotification(r);
+            sampleTagToExclude = r.sbn.getTag();
+            sampleIdToExclude = i;
         }
 
         // another package
@@ -2645,55 +2776,49 @@
         mService.addNotification(otherPackage);
 
         // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
+        // anything that's currently enqueued or posted
         int userId = new UserHandle(mUid).getIdentifier();
         assertEquals(40,
                 mService.getNotificationCountLocked(PKG, userId, 0, null));
         assertEquals(40,
                 mService.getNotificationCountLocked(PKG, userId, 0, "tag2"));
+
+        // return all for package "a" - "banana" tag isn't used
         assertEquals(2,
                 mService.getNotificationCountLocked("a", userId, 0, "banana"));
 
         // exclude a known notification - it's excluded from only the posted list, not enqueued
-        assertEquals(39,
-                mService.getNotificationCountLocked(PKG, userId, 0, "tag"));
+        assertEquals(39, mService.getNotificationCountLocked(
+                PKG, userId, sampleIdToExclude, sampleTagToExclude));
     }
 
     @Test
     public void testAddAutogroup_requestsSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
         mService.addAutogroupKeyLocked(r.getKey());
 
-        verify(rh, times(1)).requestSort();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testRemoveAutogroup_requestsSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
         mService.addNotification(r);
         mService.removeAutogroupKeyLocked(r.getKey());
 
-        verify(rh, times(1)).requestSort();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testReaddAutogroup_noSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
         mService.addNotification(r);
         mService.addAutogroupKeyLocked(r.getKey());
 
-        verify(rh, never()).requestSort();
+        verify(mRankingHandler, never()).requestSort();
     }
 
     @Test
@@ -2867,7 +2992,8 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag",
+        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+                "testBumpFGImportance_noChannelChangePreOApp",
                 Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
         mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
@@ -2883,10 +3009,12 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", Binder.getCallingUid(),
+        sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+                "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(),
                 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
-        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
+        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
+                "testBumpFGImportance_noChannelChangePreOApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(IMPORTANCE_LOW,
@@ -3099,9 +3227,6 @@
     public void testUserSentimentChangeTriggersUpdate() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3113,16 +3238,13 @@
 
         waitForIdle();
 
-        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
+        verify(mRankingHandler, timeout(300).times(1)).requestSort();
     }
 
     @Test
     public void testTooLateAdjustmentTriggersUpdate() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3134,16 +3256,13 @@
 
         waitForIdle();
 
-        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addEnqueuedNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3153,8 +3272,7 @@
                 r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
-        assertEquals(USER_SENTIMENT_NEGATIVE,
-                r.getUserSentiment());
+        assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment());
     }
 
     @Test
@@ -3186,6 +3304,8 @@
     @Test
     public void testBackup() throws Exception {
         int systemChecks = mService.countSystemChecks;
+        when(mListeners.queryPackageForServices(anyString(), anyInt(), anyInt()))
+                .thenReturn(new ArraySet<>());
         mBinderService.getBackupPayload(1);
         assertEquals(1, mService.countSystemChecks - systemChecks);
     }
@@ -4044,7 +4164,8 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         try {
-            mInternalService.enqueueNotification(notReal, "android", 0, 0, "tag",
+            mInternalService.enqueueNotification(notReal, "android", 0, 0,
+                    "testPostFromAndroidForNonExistentPackage",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
             fail("can't post notifications for nonexistent packages, even if you exist");
         } catch (SecurityException e) {
@@ -4318,7 +4439,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.sbn.getId(),
+                r.sbn.getTag(), mUid, 0,
                 new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(),
                 new UserHandle(mUid), null, 0);
         NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -4377,16 +4499,20 @@
     public void setDefaultAssistantForUser_fromConfigXml() {
         clearDeviceConfig();
         ComponentName xmlConfig = new ComponentName("config", "xml");
+        ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(xmlConfig));
         when(mResources
                 .getString(
                         com.android.internal.R.string.config_defaultAssistantAccessComponent))
                 .thenReturn(xmlConfig.flattenToString());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
-                .thenReturn(Collections.singleton(xmlConfig));
+        when(mAssistants.queryPackageForServices(eq(null), anyInt(), anyInt()))
+                .thenReturn(components);
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(components);
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
+
         mService.setDefaultAssistantForUser(0);
 
         verify(mNotificationAssistantAccessGrantedCallback)
@@ -4402,8 +4528,10 @@
                 .getString(com.android.internal.R.string.config_defaultAssistantAccessComponent))
                 .thenReturn(xmlConfig.flattenToString());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
+        when(mAssistants.queryPackageForServices(eq(null), anyInt(), anyInt()))
                 .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig)));
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(new ArraySet<>(Arrays.asList(deviceConfig)));
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
@@ -4424,7 +4552,9 @@
         when(mContext.getResources()).thenReturn(mResources);
         // Only xmlConfig is valid, deviceConfig is not.
         when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
-                .thenReturn(Collections.singleton(xmlConfig));
+                .thenReturn(new ArraySet<>(Collections.singleton(xmlConfig)));
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig)));
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
@@ -4435,6 +4565,53 @@
     }
 
     @Test
+    public void clearMultipleDefaultAssistantPackagesShouldEnableOnlyOne() throws RemoteException {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
+                generateResetComponentValues();
+        when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners);
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        ComponentName deviceConfig1 = new ComponentName("device", "config1");
+        ComponentName deviceConfig2 = new ComponentName("device", "config2");
+        changes.put(true, new ArrayList(Arrays.asList(deviceConfig1, deviceConfig2)));
+        changes.put(false, new ArrayList());
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mAssistants, times(1))
+                .setPackageOrComponentEnabled(
+                        eq("device/config2"),
+                        eq(0), eq(true), eq(false));
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                eq("device"), eq(0), eq(false), eq(true));
+    }
+
+    @Test
+    public void clearDefaultListenersPackageShouldEnableIt() throws RemoteException {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changedAssistants =
+                generateResetComponentValues();
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changedAssistants);
+        ComponentName deviceConfig = new ComponentName("device", "config");
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, new ArrayList(Arrays.asList(deviceConfig)));
+        changes.put(false, new ArrayList());
+        when(mListeners.resetComponents(anyString(), anyInt()))
+            .thenReturn(changes);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                eq("device"), eq(0), eq(false), eq(true));
+    }
+
+    @Test
+    public void clearDefaultDnDPackageShouldEnableIt() throws RemoteException {
+        ComponentName deviceConfig = new ComponentName("device", "config");
+        ArrayMap<Boolean, ArrayList<ComponentName>> changed = generateResetComponentValues();
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changed);
+        when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changed);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mConditionProviders, times(1)).resetPackage(
+                        eq("device"), eq(0));
+    }
+
+    @Test
     public void testFlagBubble() throws RemoteException {
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
@@ -4447,7 +4624,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4471,7 +4648,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4495,8 +4672,8 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // yes allowed, yes foreground, yes bubble
@@ -4517,7 +4694,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4538,7 +4715,7 @@
         // Send notif when we're foreground
         when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
                 nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4552,7 +4729,7 @@
 
         when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
                 nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
         waitForIdle();
 
@@ -4578,7 +4755,7 @@
         // Send notif when we're foreground
         when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
                 nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4587,7 +4764,7 @@
                 nr1.sbn.getKey()).getNotification().isBubbleNotification());
 
         // Remove the bubble
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", nr1.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(),
                 nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4601,7 +4778,7 @@
 
         when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
                 nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
         waitForIdle();
 
@@ -4647,11 +4824,12 @@
                 .setActions(replyAction)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_flag_messaging", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4680,13 +4858,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_flag_phonecall", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4719,7 +4898,7 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4743,13 +4922,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_noPerson", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4777,13 +4957,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_noCategory", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4817,12 +4998,13 @@
                 )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4840,7 +5022,7 @@
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4874,12 +5056,13 @@
                 )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4908,13 +5091,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_notAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4943,13 +5127,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4978,7 +5163,8 @@
         nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppCancelNotifications_cancelsBubbles",
                 nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
         waitForIdle();
 
@@ -4986,7 +5172,8 @@
         assertEquals(1, notifs.length);
         assertEquals(1, mService.getNotificationRecordCount());
 
-        mBinderService.cancelNotificationWithTag(PKG, PKG, null, nrBubble.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testAppCancelNotifications_cancelsBubbles", nrBubble.sbn.getId(),
                 nrBubble.sbn.getUserId());
         waitForIdle();
 
@@ -5141,7 +5328,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5171,7 +5358,7 @@
         // Plain notification that has bubble metadata
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5205,7 +5392,7 @@
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5235,7 +5422,7 @@
         // Plain notification that has bubble metadata
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5318,4 +5505,161 @@
 
         verify(mUsageStats, times(5)).registerImageRemoved(PKG);
     }
+
+    public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we're not foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we're not foreground
+        assertFalse(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertFalse(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
+            throws RemoteException {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we are in the foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we are foreground
+        assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Dismiss summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(),
+                nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(),
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Click summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nrSummary.getKey(), nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
 }
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 365cd80..80439cf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -60,7 +60,9 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
@@ -154,8 +156,7 @@
         contentResolver.setFallbackToExisting(false);
         Secure.putIntForUser(contentResolver,
                 Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID_N_MR1));
-        Secure.putIntForUser(contentResolver,
-                Secure.NOTIFICATION_BUBBLES, 1, UserHandle.getUserId(UID_N_MR1));
+        Global.putInt(contentResolver, Global.NOTIFICATION_BUBBLES, 1);
 
         ContentProvider testContentProvider = mock(ContentProvider.class);
         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
@@ -1950,42 +1951,18 @@
 
     @Test
     public void testBubblesOverrideTrue() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 1);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertTrue(mHelper.bubblesEnabled(USER));
+        assertTrue(mHelper.bubblesEnabled());
     }
 
     @Test
     public void testBubblesOverrideFalse() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 0);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-    }
-
-    @Test
-    public void testBubblesForUserAll() {
-        try {
-            mHelper.bubblesEnabled(UserHandle.ALL);
-        } catch (Exception e) {
-            fail("just don't throw");
-        }
-    }
-
-    @Test
-    public void testBubblesOverrideUserIsolation() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER2.getIdentifier());
-        mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-        assertTrue(mHelper.bubblesEnabled(USER2));
+        assertFalse(mHelper.bubblesEnabled());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index f37ff11..7f9f489 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -129,7 +129,7 @@
         mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
 
         try {
-            mService.init(mock(Looper.class),
+            mService.init(mock(Looper.class), mock(RankingHandler.class),
                     mock(IPackageManager.class), mock(PackageManager.class),
                     mock(LightsManager.class),
                     mock(NotificationListeners.class), mock(NotificationAssistants.class),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 977dd8e..aaaa7a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -47,6 +47,7 @@
 import org.mockito.ArgumentMatcher;
 
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for the {@link ActivityMetricsLaunchObserver} class.
@@ -118,7 +119,7 @@
 
     static <T> T verifyAsync(T mock) {
         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
-        return verify(mock, timeout(100));
+        return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 13748cb..30c8eb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -30,6 +30,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -99,6 +100,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.concurrent.TimeUnit;
@@ -111,6 +113,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class ActivityRecordTests extends ActivityTestsBase {
     private ActivityStack mStack;
     private TaskRecord mTask;
@@ -643,7 +646,7 @@
         // The override configuration should be reset and the activity's process will be killed.
         assertFalse(mActivity.inSizeCompatMode());
         verify(mActivity).restartProcessIfVisible();
-        mService.mH.runWithScissors(() -> { }, TimeUnit.SECONDS.toMillis(3));
+        mLockRule.runWithScissors(mService.mH, () -> { }, TimeUnit.SECONDS.toMillis(3));
         verify(mService.mAmInternal).killProcess(
                 eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
     }
@@ -655,6 +658,7 @@
 
                     @Override
                     public void onAnimationStart(RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
                             IRemoteAnimationFinishedCallback finishedCallback) {
 
                     }
@@ -797,6 +801,32 @@
     }
 
     /**
+     * Verify that when finishing the top focused activity on top display, the stack order will be
+     * changed by adjusting focus.
+     */
+    @Test
+    public void testFinishActivityIfPossible_adjustStackOrder() {
+        // Prepare the stacks with order (top to bottom): mStack, stack1, stack2.
+        final ActivityStack stack1 = new StackBuilder(mRootActivityContainer).build();
+        mStack.moveToFront("test");
+        // The stack2 is needed here for moving back to simulate the
+        // {@link ActivityDisplay#mPreferredTopFocusableStack} is cleared, so
+        // {@link ActivityDisplay#getFocusedStack} will rely on the order of focusable-and-visible
+        // stacks. Then when mActivity is finishing, its stack will be invisible (no running
+        // activities in the stack) that is the key condition to verify.
+        final ActivityStack stack2 = new StackBuilder(mRootActivityContainer).build();
+        stack2.moveToBack("test", stack2.getChildAt(0));
+
+        assertTrue(mStack.isTopStackOnDisplay());
+
+        mActivity.setState(RESUMED, "test");
+        mActivity.finishIfPossible(0 /* resultCode */, null /* resultData */, "test",
+                false /* oomAdj */);
+
+        assertTrue(stack1.isTopStackOnDisplay());
+    }
+
+    /**
      * Verify that resumed activity is paused due to finish request.
      */
     @Test
@@ -866,7 +896,7 @@
         mActivity.setState(PAUSED, "test");
         mActivity.finishIfPossible("test", false /* oomAdj */);
 
-        verify(mActivity).setVisibility(eq(false));
+        verify(mActivity, atLeast(1)).setVisibility(eq(false));
         verify(mActivity.getDisplay().mDisplayContent)
                 .prepareAppTransition(eq(TRANSIT_TASK_CLOSE), eq(false) /* alwaysKeepCurrent */);
         verify(mActivity.getDisplay().mDisplayContent).executeAppTransition();
@@ -1031,6 +1061,7 @@
         // simulates finishing in non-focused stack in split-screen.
         final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
         stack.getChildAt(0).getChildAt(0).nowVisible = true;
+        stack.getChildAt(0).getChildAt(0).visible = true;
 
         topActivity.completeFinishing("test");
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 60c5f0b..2a8b4c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -25,7 +25,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -979,6 +981,19 @@
     }
 
     @Test
+    public void testCompletePauseOnResumeWhilePausingActivity() {
+        final ActivityRecord bottomActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        doReturn(true).when(bottomActivity).attachedToProcess();
+        mStack.mPausingActivity = null;
+        mStack.mResumedActivity = bottomActivity;
+        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
+
+        mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity);
+        verify(mStack).completePauseLocked(anyBoolean(), eq(topActivity));
+    }
+
+    @Test
     public void testAdjustFocusedStackToHomeWhenNoActivity() {
         final ActivityStack homeStask = mDefaultDisplay.getHomeStack();
         TaskRecord homeTask = homeStask.topTask();
@@ -1122,6 +1137,22 @@
         assertThat(result).isEqualTo(taskTop);
     }
 
+    @Test
+    public void testNonTopVisibleActivityNotResume() {
+        final ActivityRecord nonTopVisibleActivity =
+                new ActivityBuilder(mService).setTask(mTask).build();
+        new ActivityBuilder(mService).setTask(mTask).build();
+        doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
+        doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleIgnoringKeyguard(anyBoolean());
+        doNothing().when(mSupervisor).startSpecificActivityLocked(any(), anyBoolean(),
+                anyBoolean());
+
+        mStack.ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
+                false /* preserveWindows */);
+        verify(mSupervisor).startSpecificActivityLocked(any(), eq(false) /* andResume */,
+                anyBoolean());
+    }
+
     private void verifyShouldSleepActivities(boolean focusedStack,
             boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
         final ActivityDisplay display = mock(ActivityDisplay.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 81fbfe4..1f672c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -83,6 +83,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests for the {@link ActivityStarter} class.
@@ -92,6 +93,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class ActivityStarterTests extends ActivityTestsBase {
     private ActivityStarter mStarter;
     private ActivityStartController mController;
@@ -145,8 +147,8 @@
         assertThat((Object) task2.getStack()).isInstanceOf(ActivityStack.class);
         mStarter.updateBounds(task2, bounds);
 
-        verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId),
-                eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt());
+        verify(mService, times(1)).animateResizePinnedStack(eq(task2.getStack().mStackId),
+                eq(bounds), anyInt());
 
         // In the case of no animation, the stack and task bounds should be set immediately.
         if (!ANIMATE) {
@@ -352,7 +354,7 @@
             doReturn(stack).when(mRootActivityContainer)
                     .getLaunchStack(any(), any(), any(), anyBoolean());
             doReturn(stack).when(mRootActivityContainer)
-                    .getLaunchStack(any(), any(), any(), anyBoolean(), any());
+                    .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
         }
 
         // Set up mock package manager internal and make sure no unmocked methods are called
@@ -364,7 +366,7 @@
 
         // Never review permissions
         doReturn(false).when(mockPackageManager).isPermissionsReviewRequired(any(), anyInt());
-        doNothing().when(mockPackageManager).grantEphemeralAccess(
+        doNothing().when(mockPackageManager).grantImplicitAccess(
                 anyInt(), any(), anyInt(), anyInt());
 
         final Intent intent = new Intent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index a5dc241..d311dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -38,7 +38,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -62,6 +61,10 @@
     @Rule
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
 
+    @WindowTestRunner.MethodWrapperRule
+    public final WindowManagerGlobalLockRule mLockRule =
+            new WindowManagerGlobalLockRule(mSystemServicesTestRule);
+
     final Context mContext = getInstrumentation().getTargetContext();
 
     ActivityTaskManagerService mService;
@@ -136,6 +139,8 @@
         private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
         private boolean mLaunchTaskBehind;
         private int mConfigChanges;
+        private int mLaunchedFromPid;
+        private int mLaunchedFromUid;
 
         ActivityBuilder(ActivityTaskManagerService service) {
             mService = service;
@@ -211,6 +216,16 @@
             return this;
         }
 
+        ActivityBuilder setLaunchedFromPid(int pid) {
+            mLaunchedFromPid = pid;
+            return this;
+        }
+
+        ActivityBuilder setLaunchedFromUid(int uid) {
+            mLaunchedFromUid = uid;
+            return this;
+        }
+
         ActivityRecord build() {
             if (mComponent == null) {
                 final int id = sCurrentActivityId++;
@@ -247,10 +262,11 @@
             }
 
             final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
-                    0 /* launchedFromPid */, 0, null, intent, null,
-                    aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
-                    0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
-                    mService.mStackSupervisor, options, null /* sourceRecord */);
+                    mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */,
+                    null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */,
+                    null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/,
+                    false /* rootVoiceInteraction */, mService.mStackSupervisor, options,
+                    null /* sourceRecord */);
             spyOn(activity);
             if (mTaskRecord != null) {
                 // fullscreen value is normally read from resources in ctor, so for testing we need
@@ -429,12 +445,7 @@
             final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
             if (mWindowingMode == WINDOWING_MODE_PINNED) {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
-                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-                };
+                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop);
             } else {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
                         mWindowingMode, mActivityType, mOnTop);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 035568f..629a954 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -75,6 +75,7 @@
     class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) {
             for (RemoteAnimationTarget target : apps) {
                 assertNotNull(target.startBounds);
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 c162b6a..45e6890 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -248,6 +248,7 @@
         boolean mCancelled = false;
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index f6f8811..703ebc9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -51,14 +51,13 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.MockitoSession;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.ByteArrayInputStream;
@@ -78,6 +77,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class DisplayWindowSettingsTests extends WindowTestsBase {
 
     private static final File TEST_FOLDER = getInstrumentation().getTargetContext().getCacheDir();
@@ -444,8 +444,6 @@
         mTarget.setUserRotation(mPrimaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
                 Surface.ROTATION_0);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -454,15 +452,12 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_DEFAULT));
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testSetFixedToUserRotationDisabled() {
         mTarget.setFixedToUserRotation(mPrimaryDisplay, FIXED_TO_USER_ROTATION_DISABLED);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -471,15 +466,12 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_DISABLED));
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testSetFixedToUserRotationEnabled() {
         mTarget.setFixedToUserRotation(mPrimaryDisplay, FIXED_TO_USER_ROTATION_ENABLED);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -488,7 +480,6 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_ENABLED));
-        mockitoSession.finishMocking();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index daee911..fa83f85 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -128,9 +128,9 @@
 
         @Override
         public String getProperty(String namespace, String name) {
-            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)
+            if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)
                     || !KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
-                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+                throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
                         + "supported.");
             }
             return mBlacklist;
@@ -140,8 +140,8 @@
         public void addOnPropertiesChangedListener(String namespace, Executor executor,
                 DeviceConfig.OnPropertiesChangedListener listener) {
 
-            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)) {
-                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+            if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)) {
+                throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
                         + "supported.");
             }
             mListeners.add(new Pair<>(listener, executor));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
new file mode 100644
index 0000000..f3a8e1a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSourceControl;
+import android.view.test.InsetsModeSession;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class InsetsPolicyTest extends WindowTestsBase {
+    private static InsetsModeSession sInsetsModeSession;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        // To let the insets provider control the insets visibility, the insets mode has to be
+        // NEW_INSETS_MODE_FULL.
+        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        sInsetsModeSession.close();
+    }
+
+    @Test
+    public void testControlsForDispatch_regular() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app can control both system bars.
+        assertNotNull(controls);
+        assertEquals(2, controls.length);
+    }
+
+    @Test
+    public void testControlsForDispatch_dockedStackVisible() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final WindowState win = createWindowOnStack(null, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The app must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_freeformStackVisible() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final WindowState win = createWindowOnStack(null, WINDOWING_MODE_FREEFORM,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The app must not control any bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_dockedDividerControllerResizing() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+        mDisplayContent.getDockedDividerController().setResizing(true);
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_keyguard() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |= PRIVATE_FLAG_KEYGUARD;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the top bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    // TODO: adjust this test if we pretend to the app that it's still able to control it.
+    @Test
+    public void testControlsForDispatch_forceStatusBarVisibleTransparent() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+                PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the top bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    @Test
+    public void testControlsForDispatch_statusBarForceShowNavigation() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+                PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the navigation bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    private WindowState addWindow(int type, String name) {
+        final WindowState win = createWindow(null, type, name);
+        mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+        return win;
+    }
+
+    private InsetsSourceControl[] addAppWindowAndGetControlsForDispatch() {
+        return addWindowAndGetControlsForDispatch(addWindow(TYPE_APPLICATION, "app"));
+    }
+
+    private InsetsSourceControl[] addWindowAndGetControlsForDispatch(WindowState win) {
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
+        return mDisplayContent.getInsetsStateController().getControlsForDispatch(win);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 86ee75e..3e2e438 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -31,14 +31,15 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class InsetsSourceProviderTest extends WindowTestsBase {
 
     private InsetsSource mSource = new InsetsSource(TYPE_TOP_BAR);
@@ -53,7 +54,7 @@
 
     @Test
     public void testPostLayout() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         topBar.mHasSurface = true;
         mProvider.setWindow(topBar, null);
@@ -66,7 +67,7 @@
 
     @Test
     public void testPostLayout_invisible() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
         mProvider.onPostLayout();
@@ -76,7 +77,7 @@
 
     @Test
     public void testPostLayout_frameProvider() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar,
                 (displayFrames, windowState, rect) -> {
@@ -88,19 +89,32 @@
 
     @Test
     public void testUpdateControlForTarget() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
         mProvider.updateControlForTarget(target, false /* force */);
-        assertNotNull(mProvider.getControl());
+        assertNotNull(mProvider.getControl(target));
         mProvider.updateControlForTarget(null, false /* force */);
-        assertNull(mProvider.getControl());
+        assertNull(mProvider.getControl(target));
+    }
+
+    @Test
+    public void testUpdateControlForFakeTarget() {
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+        topBar.getFrameLw().set(0, 0, 500, 100);
+        mProvider.setWindow(topBar, null);
+        mProvider.updateControlForFakeTarget(target);
+        assertNotNull(mProvider.getControl(target));
+        assertNull(mProvider.getControl(target).getLeash());
+        mProvider.updateControlForFakeTarget(null);
+        assertNull(mProvider.getControl(target));
     }
 
     @Test
     public void testInsetsModified() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
@@ -113,7 +127,7 @@
 
     @Test
     public void testInsetsModified_noControl() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 9fce78b..81ea32b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -23,13 +23,15 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.ViewRootImpl;
+import android.view.test.InsetsModeSession;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
@@ -37,90 +39,91 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
+@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class InsetsStateControllerTest extends WindowTestsBase {
-    private static int sPreviousNewInsetsMode;
+    private static InsetsModeSession sInsetsModeSession;
 
     @BeforeClass
     public static void setUpOnce() {
-        // TODO: Make use of SettingsSession when it becomes feasible for this.
-        sPreviousNewInsetsMode = ViewRootImpl.sNewInsetsMode;
         // To let the insets provider control the insets visibility, the insets mode has to be
         // NEW_INSETS_MODE_FULL.
-        ViewRootImpl.sNewInsetsMode = NEW_INSETS_MODE_FULL;
+        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
     }
 
     @AfterClass
     public static void tearDownOnce() {
-        ViewRootImpl.sNewInsetsMode = sPreviousNewInsetsMode;
+        sInsetsModeSession.close();
     }
 
     @Test
     @FlakyTest(bugId = 131005232)
     public void testStripForDispatch_notOwn() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR));
         assertNotNull(getController().getInsetsForDispatch(app).getSource(TYPE_TOP_BAR));
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testStripForDispatch_own() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .setWindow(topBar, null);
         topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR));
-        assertEquals(new InsetsState(), getController().getInsetsForDispatch(topBar));
+        final InsetsState state = getController().getInsetsForDispatch(topBar);
+        for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            assertNotEquals(TYPE_TOP_BAR, source.getType());
+        }
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testStripForDispatch_navBar() {
-        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState ime = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null);
         getController().getSourceProvider(TYPE_IME).setWindow(ime, null);
-        assertEquals(new InsetsState(), getController().getInsetsForDispatch(navBar));
+        assertEquals(0, getController().getInsetsForDispatch(navBar).getSourcesCount());
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testBarControllingWinChanged() {
-        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, app);
         InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
         assertEquals(2, controls.length);
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testControlRevoked() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, null);
         assertNotNull(getController().getControlsForDispatch(app));
-        getController().onBarControllingWindowChanged(null);
+        getController().onBarControlTargetChanged(null, null);
         assertNull(getController().getControlsForDispatch(app));
     }
 
     @FlakyTest(bugId = 124088319)
     @Test
     public void testControlRevoked_animation() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, null);
         assertNotNull(getController().getControlsForDispatch(app));
         topBar.cancelAnimation();
         assertNull(getController().getControlsForDispatch(app));
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index c52c8d7..2d0416d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -48,7 +48,7 @@
     @Before
     public void setUp() throws Exception {
         mSurfaces = new SurfaceControlMocker();
-        mLetterbox = new Letterbox(mSurfaces);
+        mLetterbox = new Letterbox(mSurfaces, () -> mock(SurfaceControl.Transaction.class));
         mTransaction = mock(SurfaceControl.Transaction.class);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index efd468f..e9c2263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -66,8 +66,8 @@
 
         verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(false), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(false));
         verify(mIPinnedStackListener).onActionsChanged(any());
         verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
 
@@ -75,8 +75,8 @@
 
         mWm.setShelfHeight(true, SHELF_HEIGHT);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(true), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(true));
         verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
new file mode 100644
index 0000000..8eecff5
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.protolog.ProtoLogImpl;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Check if the ProtoLogTools is used to process the WindowManager source code.
+ */
+@SmallTest
+@Presubmit
+public class ProtoLogIntegrationTest {
+    @After
+    public void tearDown() {
+        ProtoLogImpl.setSingleInstance(null);
+    }
+
+    @Test
+    public void testProtoLogToolIntegration() {
+        ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+        ProtoLogImpl.setSingleInstance(mockedProtoLog);
+        ProtoLogGroup.testProtoLog();
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(
+                ProtoLogGroup.TEST_GROUP),
+                eq(485522692), eq(0b0010101001010111),
+                eq(ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+                        ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
+                        : null),
+                eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index fb4e330..94abd34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -62,6 +62,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.util.MutableLong;
 import android.util.SparseBooleanArray;
 
@@ -1003,7 +1004,6 @@
     public void testRecentsComponent_allowApiAccessWithoutPermissions() {
         doReturn(PackageManager.PERMISSION_DENIED).when(mService)
                 .checkGetTasksPermission(anyString(), anyInt(), anyInt());
-
         // Set the recents component and ensure that the following calls do not fail
         mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
         doTestRecentTasksApis(true /* expectNoSecurityException */);
@@ -1031,7 +1031,7 @@
         assertSecurityException(expectCallable,
                 () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
         assertSecurityException(expectCallable,
-                () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+                () -> mService.animateResizePinnedStack(INVALID_STACK_ID, new Rect(), -1));
         assertSecurityException(expectCallable,
                 () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
                         new Rect()));
@@ -1289,10 +1289,10 @@
         @Override
         void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
                 int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
-                int callingUid, boolean allowed) {
+                int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
             mLastAllowed = allowed;
             super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
-                    callingUid, allowed);
+                    callingUid, allowed, crossUser, profileIds);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 9ca0180..f792b0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -26,13 +26,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -44,9 +48,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.TaskSnapshot;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseBooleanArray;
@@ -88,8 +95,8 @@
             doReturn(mDisplayContent).when(mWm.mRoot).getDisplayContent(anyInt());
         }
         when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
-                DEFAULT_DISPLAY);
+        mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
+                DEFAULT_DISPLAY));
     }
 
     @Test
@@ -133,11 +140,11 @@
     @Test
     public void testIncludedApps_expectTargetAndVisible() {
         mWm.setRecentsAnimationController(mController);
-        final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final AppWindowToken homeAppWindow =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
-                        .setStack(homStack)
+                        .setStack(homeStack)
                         .setCreateTask(true)
                         .build()
                         .mAppWindowToken;
@@ -157,6 +164,102 @@
     }
 
     @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Ensure that we are animating the app and wallpaper target
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Cancel the animation and ensure the controller is still running
+        wallpaperWindowToken.cancelAnimation();
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
+        verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */);
+    }
+
+    @Test
+    public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeAppWindow, "hwin1");
+        homeAppWindow.addWindow(hwin1);
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        // Start and finish the animation
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+        // Reset at this point since we may remove adapters that couldn't be created
+        reset(mController);
+        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
+
+        // Ensure that we remove the task (home & app) and wallpaper adapters
+        verify(mController, times(2)).removeAnimation(any());
+        verify(mController, times(1)).removeWallpaperAnimation(any());
+    }
+
+    @Test
     public void testDeferCancelAnimation() throws Exception {
         mWm.setRecentsAnimationController(mController);
         final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index dcc295c..b4ccd50 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -56,6 +56,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Build/Install/Run:
@@ -63,6 +64,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class RecentsAnimationTest extends ActivityTestsBase {
 
     private static final int TEST_USER_ID = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 3e05dcc..3b9c3bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -19,7 +19,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -27,10 +29,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -96,9 +100,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(new Point(50, 100), app.position);
@@ -201,9 +208,12 @@
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
         final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                 ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+        final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
         final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                 ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                finishedCaptor.capture());
         assertEquals(1, appsCaptor.getValue().length);
         assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
     }
@@ -237,9 +247,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
@@ -264,6 +277,66 @@
         }
     }
 
+    @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+
+            // Cancel the wallpaper window animator and ensure the runner is not canceled
+            wallpaperWindowToken.cancelAnimation();
+            verify(mMockRunner, never()).onAnimationCancelled();
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 539a79c..aa97de72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.TYPE_VIRTUAL;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -61,6 +62,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
+import android.view.DisplayInfo;
 
 import androidx.test.filters.MediumTest;
 
@@ -69,6 +71,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -82,6 +85,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class RootActivityContainerTests extends ActivityTestsBase {
     private ActivityStack mFullscreenStack;
 
@@ -575,7 +579,7 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+        final TestActivityDisplay secondDisplay = createNewActivityDisplay();
         mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
 
         doReturn(true).when(secondDisplay).supportsSystemDecorations();
@@ -601,7 +605,7 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+        final TestActivityDisplay secondDisplay = createNewActivityDisplay();
         mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
         doReturn(false).when(secondDisplay).supportsSystemDecorations();
 
@@ -818,6 +822,41 @@
     }
 
     /**
+     * Test that {@link RootActivityContainer#getLaunchStack} with the real caller id will get the
+     * expected stack when requesting the activity launch on the secondary display.
+     */
+    @Test
+    public void testGetLaunchStackWithRealCallerId() {
+        // Create a non-system owned virtual display.
+        final DisplayInfo info = new DisplayInfo();
+        mSupervisor.mService.mContext.getDisplay().getDisplayInfo(info);
+        info.type = TYPE_VIRTUAL;
+        info.ownerUid = 100;
+        final TestActivityDisplay secondaryDisplay = TestActivityDisplay.create(mSupervisor, info);
+        mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP);
+
+        // Create an activity with specify the original launch pid / uid.
+        final ActivityRecord r = new ActivityBuilder(mService).setLaunchedFromPid(200)
+                .setLaunchedFromUid(200).build();
+
+        // Simulate ActivityStarter to find a launch stack for requesting the activity to launch
+        // on the secondary display with realCallerId.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
+                300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
+        final ActivityStack result = mRootActivityContainer.getLaunchStack(r, options,
+                null /* task */, true /* onTop */, null, 300 /* test realCallerPid */,
+                300 /* test realCallerUid */);
+
+        // Assert that the stack is returned as expected.
+        assertNotNull(result);
+        assertEquals("The display ID of the stack should same as secondary display ",
+                secondaryDisplay.mDisplayId, result.mDisplayId);
+    }
+
+    /**
      * Mock {@link RootActivityContainer#resolveHomeActivity} for returning consistent activity
      * info for test cases (the original implementation will resolve from the real package manager).
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index dc96480..3e316f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 
 import androidx.test.filters.MediumTest;
 
@@ -45,6 +46,8 @@
 @Presubmit
 public class RunningTasksTest extends ActivityTestsBase {
 
+    private static final ArraySet<Integer> PROFILE_IDS = new ArraySet<>();
+
     private RunningTasks mRunningTasks;
 
     @Before
@@ -77,7 +80,8 @@
         final int numFetchTasks = 5;
         ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
         mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
-                displays, -1 /* callingUid */, true /* allowed */);
+                displays, -1 /* callingUid */, true /* allowed */, true /*crossUser */,
+                PROFILE_IDS);
         assertThat(tasks).hasSize(numFetchTasks);
         for (int i = 0; i < numFetchTasks; i++) {
             assertEquals(numTasks - i - 1, tasks.get(i).id);
@@ -87,7 +91,8 @@
         // and does not crash
         tasks.clear();
         mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
-                displays, -1 /* callingUid */, true /* allowed */);
+                displays, -1 /* callingUid */, true /* allowed */, true /* crossUser */,
+                PROFILE_IDS);
         assertThat(tasks).hasSize(numTasks);
         for (int i = 0; i < numTasks; i++) {
             assertEquals(numTasks - i - 1, tasks.get(i).id);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 83dd69a..8d2a79b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -185,11 +185,6 @@
     }
 
     @Override
-    public SurfaceControl.Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
-        return this;
-    }
-
-    @Override
     public SurfaceControl.Transaction setSecure(SurfaceControl sc, boolean isSecure) {
         return this;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 340e741..2b1c4ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -138,6 +138,19 @@
     }
 
     @Test
+    public void testCancelWithNullFinishCallbackAnimation() {
+        SurfaceAnimator animator = new SurfaceAnimator(mAnimatable, null, mWm);
+        animator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        assertTrue(animator.isAnimating());
+        assertNotNull(animator.getAnimation());
+        animator.cancelAnimation();
+        assertFalse(animator.isAnimating());
+        assertNull(animator.getAnimation());
+        verify(mSpec).onAnimationCancelled(any());
+        verify(mTransaction).remove(eq(mAnimatable.mLeash));
+    }
+
+    @Test
     public void testDelayingAnimationStart() {
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 1ad0e00..5a4d399 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
+import android.os.StrictMode;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.view.InputChannel;
@@ -73,6 +74,7 @@
 import com.android.server.appop.AppOpsService;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -99,6 +101,7 @@
     static int sNextTaskId = 100;
 
     private final AtomicBoolean mCurrentMessagesProcessed = new AtomicBoolean(false);
+    private static final int[] TEST_USER_PROFILE_IDS = {};
 
     private Context mContext;
     private StaticMockitoSession mMockitoSession;
@@ -228,7 +231,7 @@
                 new AMTestInjector(mContext, mHandlerThread), mHandlerThread);
         spyOn(mAmService);
         doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager();
-        doNothing().when(mAmService).grantEphemeralAccessLocked(
+        doNothing().when(mAmService).grantImplicitAccess(
                 anyInt(), any(), anyInt(), anyInt());
 
         // ActivityManagerInternal
@@ -253,18 +256,17 @@
         mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
         mWMPolicy = new TestWindowManagerPolicy(this::getWindowManagerService,
                 mPowerManagerWrapper);
+        // Suppress StrictMode violation (DisplayWindowSettings) to avoid log flood.
+        DisplayThread.getHandler().post(StrictMode::allowThreadDiskWritesMask);
         mWmService = WindowManagerService.main(
-                mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new);
+                mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new,
+                () -> mock(Surface.class), (unused) -> new MockSurfaceControlBuilder());
         spyOn(mWmService);
 
         // Setup factory classes to prevent calls to native code.
         mTransaction = spy(StubTransaction.class);
         // Return a spied Transaction class than can be used to verify calls.
         mWmService.mTransactionFactory = () -> mTransaction;
-        // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances.
-        mWmService.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder();
-        // Return mocked Surface instances.
-        mWmService.mSurfaceFactory = () -> mock(Surface.class);
         mWmService.mSurfaceAnimationRunner = new SurfaceAnimationRunner(
                 null, null, mTransaction, mWmService.mPowerManagerInternal);
 
@@ -424,6 +426,11 @@
             doReturn(AppOpsManager.MODE_DEFAULT)
                     .when(aos).noteOperation(anyInt(), anyInt(), anyString());
 
+            // UserManagerService
+            final UserManagerService ums = mock(UserManagerService.class);
+            doReturn(ums).when(this).getUserManager();
+            doReturn(TEST_USER_PROFILE_IDS).when(ums).getProfileIds(anyInt(), eq(true));
+
             setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class));
             ams.mActivityTaskManager = this;
             ams.mAtmInternal = mInternal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fe..6e41118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@
         waitForCallback(singleTaskDisplayDrawnLatch);
     }
 
+    @Test
+    public void testSingleTaskDisplayEmpty() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+
+        final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+        final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+                singleTaskDisplayDrawnLatch.countDown();
+            }
+            @Override
+            public void onSingleTaskDisplayEmpty(int displayId)
+                    throws RemoteException {
+                singleTaskDisplayEmptyLatch.countDown();
+            }
+        });
+        final ActivityViewTestActivity activity =
+                (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+        final ActivityView activityView = activity.getActivityView();
+        activityView.setCallback(new ActivityView.StateCallback() {
+            @Override
+            public void onActivityViewReady(ActivityView view) {
+                activityViewReadyLatch.countDown();
+            }
+
+            @Override
+            public void onActivityViewDestroyed(ActivityView view) {
+                activityViewDestroyedLatch.countDown();
+            }
+        });
+        waitForCallback(activityViewReadyLatch);
+
+        final Context context = instrumentation.getContext();
+        Intent intent = new Intent(context, ActivityInActivityView.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        activityView.startActivity(intent);
+        waitForCallback(singleTaskDisplayDrawnLatch);
+        assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+        activityView.release();
+        waitForCallback(activityViewDestroyedLatch);
+        waitForCallback(singleTaskDisplayEmptyLatch);
+    }
+
     /**
      * Starts the provided activity and returns the started instance.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bb89446..761f73e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -167,12 +167,13 @@
     }
 
     @Override
-    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+    public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
+            int policyFlags) {
         return 0;
     }
 
     @Override
-    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
+    public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) {
         return null;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java
new file mode 100644
index 0000000..e0c314f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Holds {@link WindowManagerGlobalLock} for test methods (including before and after) to prevent
+ * the race condition with other threads (e.g. {@link com.android.server.DisplayThread},
+ * {@link com.android.server.AnimationThread}, {@link com.android.server.UiThread}).
+ */
+class WindowManagerGlobalLockRule implements WindowTestRunner.MethodWrapper {
+
+    private final SystemServicesTestRule mSystemServicesTestRule;
+    private volatile boolean mIsLocked;
+
+    WindowManagerGlobalLockRule(SystemServicesTestRule systemServiceTestRule) {
+        mSystemServicesTestRule = systemServiceTestRule;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        if (description.getAnnotation(NoGlobalLock.class) == null) {
+            return new StatementWrapper(base);
+        }
+        return base;
+    }
+
+    @Override
+    public FrameworkMethod apply(FrameworkMethod base) {
+        if (base.getAnnotation(NoGlobalLock.class) == null) {
+            return new FrameworkMethodWrapper(base);
+        }
+        return base;
+    }
+
+    void waitUntilHandlersIdle() {
+        if (!mIsLocked) {
+            mSystemServicesTestRule.waitUntilWindowManagerHandlersIdle();
+            return;
+        }
+
+        waitForLocked(mSystemServicesTestRule::waitUntilWindowManagerHandlersIdle);
+    }
+
+    void runWithScissors(Handler handler, Runnable r, long timeout) {
+        if (!mIsLocked) {
+            handler.runWithScissors(r, timeout);
+            return;
+        }
+
+        waitForLocked(() -> handler.runWithScissors(r, timeout));
+    }
+
+    /**
+     * If the test holds the lock, we need to invoke {@link Object#wait} to release it so other
+     * threads won't be blocked when we are waiting.
+     */
+    private void waitForLocked(Runnable r) {
+        final Object lock = mSystemServicesTestRule.getWindowManagerService().mGlobalLock;
+        final AtomicBoolean done = new AtomicBoolean(false);
+
+        AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+            r.run();
+            synchronized (lock) {
+                lock.notifyAll();
+                done.set(true);
+            }
+        });
+
+        synchronized (lock) {
+            if (!done.get()) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException impossible) {
+                }
+            }
+        }
+    }
+
+    /** Wraps methods annotated with {@link org.junit.Test}. */
+    private class StatementWrapper extends Statement {
+        final Statement mBase;
+
+        StatementWrapper(Statement base) {
+            mBase = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            try {
+                synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
+                    mIsLocked = true;
+                    mBase.evaluate();
+                }
+            } finally {
+                mIsLocked = false;
+            }
+        }
+    }
+
+    /** Wraps methods annotated with {@link org.junit.Before} or {@link org.junit.After}. */
+    private class FrameworkMethodWrapper extends FrameworkMethod {
+
+        FrameworkMethodWrapper(FrameworkMethod base) {
+            super(base.getMethod());
+        }
+
+        @Override
+        public Object invokeExplosively(Object target, Object... params) throws Throwable {
+            try {
+                synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
+                    mIsLocked = true;
+                    return super.invokeExplosively(target, params);
+                }
+            } finally {
+                mIsLocked = false;
+            }
+        }
+    }
+
+    /**
+     * If the test method is annotated with {@link NoGlobalLock}, the rule
+     * {@link WindowManagerGlobalLockRule} won't apply to the method.
+     */
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface NoGlobalLock {
+        String reason() default "";
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
new file mode 100644
index 0000000..86f0f8b
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test for {@link WindowManagerService.SettingsObserver}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:WindowManagerSettingsTests
+ */
+@SmallTest
+public class WindowManagerSettingsTests extends WindowTestsBase {
+
+    @Test
+    public void testForceDesktopModeOnExternalDisplays() {
+        try (SettingsSession forceDesktopModeSession = new
+                SettingsSession(DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS)) {
+            final boolean forceDesktopMode = !forceDesktopModeSession.getSetting();
+            final Uri forceDesktopModeUri = forceDesktopModeSession.setSetting(forceDesktopMode);
+            mWm.mSettingsObserver.onChange(false, forceDesktopModeUri);
+
+            assertEquals(mWm.mForceDesktopModeOnExternalDisplays, forceDesktopMode);
+        }
+    }
+
+    @Test
+    public void testFreeformWindow() {
+        try (SettingsSession freeformWindowSession = new
+                SettingsSession(DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT)) {
+            final boolean freeformWindow = !freeformWindowSession.getSetting();
+            final Uri freeformWindowUri = freeformWindowSession.setSetting(freeformWindow);
+            mWm.mSettingsObserver.onChange(false, freeformWindowUri);
+
+            assertEquals(mWm.mAtmService.mSupportsFreeformWindowManagement, freeformWindow);
+        }
+    }
+
+    @Test
+    public void testForceResizableMode() {
+        try (SettingsSession forceResizableSession = new
+                SettingsSession(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES)) {
+            final boolean forceResizableMode = !forceResizableSession.getSetting();
+            final Uri forceResizableUri =  forceResizableSession.setSetting(forceResizableMode);
+            mWm.mSettingsObserver.onChange(false, forceResizableUri);
+
+            assertEquals(mWm.mAtmService.mForceResizableActivities, forceResizableMode);
+        }
+    }
+
+    private class SettingsSession implements AutoCloseable {
+
+        private static final int SETTING_VALUE_OFF = 0;
+        private static final int SETTING_VALUE_ON = 1;
+        private final String mSettingName;
+        private final int mInitialValue;
+
+        SettingsSession(String name) {
+            mSettingName = name;
+            mInitialValue = getSetting() ? SETTING_VALUE_ON : SETTING_VALUE_OFF;
+        }
+
+        boolean getSetting() {
+            return Settings.Global.getInt(getContentResolver(), mSettingName, 0) != 0;
+        }
+
+        Uri setSetting(boolean enable) {
+            Settings.Global.putInt(getContentResolver(), mSettingName,
+                    enable ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+            return Settings.Global.getUriFor(mSettingName);
+        }
+
+        private ContentResolver getContentResolver() {
+            return getInstrumentation().getTargetContext().getContentResolver();
+        }
+
+        @Override
+        public void close() {
+            setSetting(mInitialValue == SETTING_VALUE_ON);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b731628..f41f126 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -78,6 +78,7 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.LinkedList;
 
@@ -89,6 +90,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class WindowStateTests extends WindowTestsBase {
     private static int sPreviousNewInsetsMode;
 
@@ -111,11 +113,8 @@
         // TODO: Let the insets source with new mode keep the visibility control, and remove this
         // setup code. Now mTopFullscreenOpaqueWindowState will take back the control of insets
         // visibility.
-        // Hold the lock to protect the mock from accesssing by other threads.
-        synchronized (mWm.mGlobalLock) {
-            spyOn(mDisplayContent);
-            doNothing().when(mDisplayContent).layoutAndAssignWindowLayersIfNeeded();
-        }
+        spyOn(mDisplayContent);
+        doNothing().when(mDisplayContent).layoutAndAssignWindowLayersIfNeeded();
     }
 
     @Test
@@ -404,7 +403,7 @@
         assertTrue(topBar.isVisible());
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .setWindow(topBar, null);
-        mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(app);
+        mDisplayContent.getInsetsStateController().onBarControlTargetChanged(app, app);
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .onInsetsModified(app, new InsetsSource(TYPE_TOP_BAR));
         waitUntilHandlersIdle();
@@ -527,27 +526,25 @@
         final float OFFSET_SUM =
                 PARENT_WINDOW_OFFSET + DISPLAY_IN_PARENT_WINDOW_OFFSET + WINDOW_OFFSET;
 
-        synchronized (mWm.mGlobalLock) {
-            final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
+        final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
 
-            final DisplayContent dc = createNewDisplay();
-            win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
-            dc.reparentDisplayContent(win0, win0.getSurfaceControl());
-            dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
+        final DisplayContent dc = createNewDisplay();
+        win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
+        dc.reparentDisplayContent(win0, win0.getSurfaceControl());
+        dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
 
-            final float[] values = new float[9];
-            final Matrix matrix = new Matrix();
-            final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-            final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
-            win1.mHasSurface = true;
-            win1.mSurfaceControl = mock(SurfaceControl.class);
-            win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
-            win1.updateSurfacePosition(t);
-            win1.getTransformationMatrix(values, matrix);
+        final float[] values = new float[9];
+        final Matrix matrix = new Matrix();
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
+        win1.mHasSurface = true;
+        win1.mSurfaceControl = mock(SurfaceControl.class);
+        win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
+        win1.updateSurfacePosition(t);
+        win1.getTransformationMatrix(values, matrix);
 
-            matrix.getValues(values);
-            assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
-            assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
-        }
+        matrix.getValues(values);
+        assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
+        assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java
new file mode 100644
index 0000000..2b801e2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A runner with support to bind additional operations with test method tightly.
+ *
+ * @see MethodWrapperRule
+ */
+public class WindowTestRunner extends AndroidJUnit4ClassRunner {
+    private final List<FrameworkMethod> mBefores;
+    private final List<FrameworkMethod> mAfters;
+
+    public WindowTestRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+        mBefores = getTestClass().getAnnotatedMethods(Before.class);
+        mAfters = getTestClass().getAnnotatedMethods(After.class);
+    }
+
+    @Override
+    protected Statement methodInvoker(FrameworkMethod method, Object test) {
+        return wrapStatement(super.methodInvoker(method, test), method, test);
+    }
+
+    private Statement wrapStatement(Statement statement, FrameworkMethod method, Object target) {
+        for (MethodWrapper wrapper : getMethodWrappers(target)) {
+            statement = wrapper.apply(statement, describeChild(method));
+        }
+        return statement;
+    }
+
+    /**
+     * Constructs the test statement with {@link Before}.
+     *
+     * @param method The test method.
+     * @param target The instance of test class.
+     * @param statement The next statement. It is usually the test method.
+     */
+    @Override
+    protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
+        if (mBefores.isEmpty()) {
+            return statement;
+        }
+
+        final List<FrameworkMethod> befores = new ArrayList<>(mBefores.size());
+        for (FrameworkMethod before : mBefores) {
+            befores.add(wrapMethod(before, target));
+        }
+        return new RunBefores(statement, befores, target);
+    }
+
+    /**
+     * Constructs the test statement with {@link After}.
+     *
+     * @param method The test method.
+     * @param target The instance of test class.
+     * @param statement The next statement. If there are "before" methods, then it is the
+     *                  before-statement for the next test.
+     */
+    @Override
+    protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
+        if (mAfters.isEmpty()) {
+            return statement;
+        }
+
+        final List<FrameworkMethod> afters = new ArrayList<>(mAfters.size());
+        for (FrameworkMethod after : mAfters) {
+            afters.add(wrapMethod(after, target));
+        }
+        return new RunAfters(statement, afters, target);
+    }
+
+    private FrameworkMethod wrapMethod(FrameworkMethod method, Object target) {
+        for (MethodWrapper wrapper : getMethodWrappers(target)) {
+            method = wrapper.apply(method);
+        }
+        return method;
+    }
+
+    private List<MethodWrapper> getMethodWrappers(Object target) {
+        return getTestClass().getAnnotatedFieldValues(
+                target, MethodWrapperRule.class, MethodWrapper.class);
+    }
+
+    /**
+     * If a {@link TestRule} is annotated with this, it can ensure the operation of the rule runs
+     * with the test method on the same path and thread.
+     * <p>
+     * The traditional {@link org.junit.Rule} may run on another thread if timeout is set. And if
+     * the rule will hold a lock which will be used in test method, it will cause deadlock such as
+     * "Instr: androidx.test.runner.AndroidJUnitRunner" and "Time-limited test" wait for each other.
+     * <p>
+     * This annotation only takes effect if the test runner is {@link WindowTestRunner}.
+     *
+     * @see org.junit.internal.runners.statements.FailOnTimeout
+     * @see org.junit.runners.BlockJUnit4ClassRunner#methodBlock
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @interface MethodWrapperRule {}
+
+    /**
+     * The interface to support wrapping test method, including {@link Before} and {@link After}.
+     */
+    interface MethodWrapper extends TestRule {
+        FrameworkMethod apply(FrameworkMethod base);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index d1cf1c3..8930e5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -98,6 +98,10 @@
     @Rule
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
 
+    @WindowTestRunner.MethodWrapperRule
+    public final WindowManagerGlobalLockRule mLockRule =
+            new WindowManagerGlobalLockRule(mSystemServicesTestRule);
+
     @BeforeClass
     public static void setUpOnceBase() {
         AttributeCache.init(getInstrumentation().getTargetContext());
@@ -205,7 +209,7 @@
      * Waits until the main handler for WM has processed all messages.
      */
     void waitUntilHandlersIdle() {
-        mSystemServicesTestRule.waitUntilWindowManagerHandlersIdle();
+        mLockRule.waitUntilHandlersIdle();
     }
 
     private WindowToken createWindowToken(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
deleted file mode 100644
index b299f0d..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoOutputStream;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.util.Preconditions;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-
-/**
- * Test class for {@link WindowTraceBuffer}.
- *
- * Build/Install/Run:
- *  atest WmTests:WindowTraceBufferTest
- */
-@SmallTest
-@Presubmit
-public class WindowTraceBufferTest {
-    private File mFile;
-    private WindowTraceBuffer mBuffer;
-
-    @Before
-    public void setUp() throws Exception {
-        final Context testContext = getInstrumentation().getContext();
-        mFile = testContext.getFileStreamPath("tracing_test.dat");
-        mFile.delete();
-
-        mBuffer = new WindowTraceBuffer(10);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mFile.delete();
-    }
-
-    @Test
-    public void test_addItem() {
-        ProtoOutputStream toWrite = getDummy(1);
-        final int objectSize = toWrite.getRawSize();
-        mBuffer.setCapacity(objectSize);
-        mBuffer.resetBuffer();
-
-        Preconditions.checkArgument(mBuffer.size() == 0);
-
-        mBuffer.add(toWrite);
-
-        assertEquals("Item was not added to the buffer", 1, mBuffer.size());
-        assertEquals("Total buffer getSize differs from inserted object",
-                mBuffer.getBufferSize(), objectSize);
-        assertEquals("Available buffer space does not match used one", 0,
-                mBuffer.getAvailableSpace());
-    }
-
-    @Test
-    public void test_addItemMustOverwriteOne() {
-        ProtoOutputStream toWrite1 = getDummy(1);
-        ProtoOutputStream toWrite2 = getDummy(2);
-        ProtoOutputStream toWrite3 = getDummy(3);
-        final int objectSize = toWrite1.getRawSize();
-        final int bufferCapacity = objectSize * 2 + 1;
-        mBuffer.setCapacity(bufferCapacity);
-        mBuffer.resetBuffer();
-
-        mBuffer.add(toWrite1);
-        byte[] toWrite1Bytes = toWrite1.getBytes();
-        assertTrue("First element should be in the list",
-                mBuffer.contains(toWrite1Bytes));
-
-        mBuffer.add(toWrite2);
-        byte[] toWrite2Bytes = toWrite2.getBytes();
-        assertTrue("First element should be in the list",
-                mBuffer.contains(toWrite1Bytes));
-        assertTrue("Second element should be in the list",
-                mBuffer.contains(toWrite2Bytes));
-
-        mBuffer.add(toWrite3);
-        byte[] toWrite3Bytes = toWrite3.getBytes();
-        assertTrue("First element should not be in the list",
-                !mBuffer.contains(toWrite1Bytes));
-        assertTrue("Second element should be in the list",
-                mBuffer.contains(toWrite2Bytes));
-        assertTrue("Third element should be in the list",
-                mBuffer.contains(toWrite3Bytes));
-        assertEquals("Buffer should have 2 elements", 2, mBuffer.size());
-        assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity),
-                mBuffer.getBufferSize(), bufferCapacity - 1);
-        assertEquals(" Buffer is full, available space should be 0", 1,
-                mBuffer.getAvailableSpace());
-    }
-
-    @Test
-    public void test_addItemMustOverwriteMultiple() {
-        ProtoOutputStream toWriteSmall1 = getDummy(1);
-        ProtoOutputStream toWriteSmall2 = getDummy(2);
-        final int objectSize = toWriteSmall1.getRawSize();
-        final int bufferCapacity = objectSize * 2;
-        mBuffer.setCapacity(bufferCapacity);
-        mBuffer.resetBuffer();
-
-        ProtoOutputStream toWriteBig = new ProtoOutputStream();
-        toWriteBig.write(MAGIC_NUMBER, 1);
-        toWriteBig.write(MAGIC_NUMBER, 2);
-
-        mBuffer.add(toWriteSmall1);
-        byte[] toWriteSmall1Bytes = toWriteSmall1.getBytes();
-        assertTrue("First element should be in the list",
-                mBuffer.contains(toWriteSmall1Bytes));
-
-        mBuffer.add(toWriteSmall2);
-        byte[] toWriteSmall2Bytes = toWriteSmall2.getBytes();
-        assertTrue("First element should be in the list",
-                mBuffer.contains(toWriteSmall1Bytes));
-        assertTrue("Second element should be in the list",
-                mBuffer.contains(toWriteSmall2Bytes));
-
-        mBuffer.add(toWriteBig);
-        byte[] toWriteBigBytes = toWriteBig.getBytes();
-        assertTrue("Third element should overwrite all others",
-                !mBuffer.contains(toWriteSmall1Bytes));
-        assertTrue("Third element should overwrite all others",
-                !mBuffer.contains(toWriteSmall2Bytes));
-        assertTrue("Third element should overwrite all others",
-                mBuffer.contains(toWriteBigBytes));
-
-        assertEquals(" Buffer should have only 1 big element", 1, mBuffer.size());
-        assertEquals(String.format(" Buffer is full, used space should be %d", bufferCapacity),
-                mBuffer.getBufferSize(), bufferCapacity);
-        assertEquals(" Buffer is full, available space should be 0", 0,
-                mBuffer.getAvailableSpace());
-    }
-
-    @Test
-    public void test_startResetsBuffer() {
-        ProtoOutputStream toWrite = getDummy(1);
-        mBuffer.resetBuffer();
-        Preconditions.checkArgument(mBuffer.size() == 0);
-
-        mBuffer.add(toWrite);
-        assertEquals("Item was not added to the buffer", 1, mBuffer.size());
-        mBuffer.resetBuffer();
-        assertEquals("Buffer should be empty after reset", 0, mBuffer.size());
-        assertEquals("Buffer size should be 0 after reset", 0, mBuffer.getBufferSize());
-    }
-
-    private ProtoOutputStream getDummy(int value) {
-        ProtoOutputStream toWrite = new ProtoOutputStream();
-        toWrite.write(MAGIC_NUMBER, value);
-        toWrite.flush();
-
-        return toWrite;
-    }
-
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 7d7c398..2105ab0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -51,6 +51,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.function.Function;
 
 /**
  * Tests for the {@link DisplayContent#assignChildLayers(SurfaceControl.Transaction)} method.
@@ -131,7 +132,8 @@
         }
     }
 
-    private static class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory {
+    private static class HierarchyRecordingBuilderFactory implements Function<SurfaceSession,
+                SurfaceControl.Builder> {
         private LayerRecordingTransaction mTransaction;
 
         HierarchyRecordingBuilderFactory(LayerRecordingTransaction transaction) {
@@ -139,7 +141,7 @@
         }
 
         @Override
-        public SurfaceControl.Builder make(SurfaceSession s) {
+        public SurfaceControl.Builder apply(SurfaceSession s) {
             final LayerRecordingTransaction transaction = mTransaction;
             return new HierarchyRecorder(s, transaction);
         }
@@ -153,7 +155,7 @@
         // which is after construction of the DisplayContent, meaning the HierarchyRecorder
         // would miss construction of the top-level layers.
         mTransaction = new LayerRecordingTransaction();
-        mWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory(mTransaction);
+        mWm.mSurfaceControlFactory = new HierarchyRecordingBuilderFactory(mTransaction);
         mWm.mTransactionFactory = () -> mTransaction;
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 3e88d93..6d3f416 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -606,4 +606,37 @@
 
         proto.flush();
     }
+
+    // TODO: move to UsageStatsProtoV2
+    static void readPendingEvents(InputStream in, List<UsageEvents.Event> events)
+            throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        final List<String> stringPool = new ArrayList<>();
+        final IntervalStats tmpStatsObj = new IntervalStats();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) IntervalStatsProto.PENDING_EVENTS:
+                    loadEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, stringPool);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    final int eventCount = tmpStatsObj.events.size();
+                    for (int i = 0; i < eventCount; i++) {
+                        events.add(tmpStatsObj.events.get(i));
+                    }
+                    return;
+            }
+        }
+    }
+
+    // TODO: move to UsageStatsProtoV2
+    static void writePendingEvents(OutputStream out, List<UsageEvents.Event> events)
+            throws IOException {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        final IntervalStats tmpStatsObj = new IntervalStats();
+        final int eventCount = events.size();
+        for (int i = 0; i < eventCount; i++) {
+            writeEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, events.get(i));
+        }
+        proto.flush();
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2bdeddf..e3183e3 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -18,14 +18,18 @@
 
 import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION;
 import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.DEVICE_EVENT_PACKAGE_NAME;
 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageEvents.Event.USER_STOPPED;
+import static android.app.usage.UsageEvents.Event.USER_UNLOCKED;
 import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
 import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
@@ -70,8 +74,10 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.content.PackageMonitor;
@@ -81,11 +87,20 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
@@ -106,11 +121,17 @@
     private static final long TEN_SECONDS = 10 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
-    private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
+    static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
 
     private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
+    private static final File USAGE_STATS_LEGACY_DIR = new File(
+            Environment.getDataSystemDirectory(), "usagestats");
+    // For migration purposes, indicates whether to keep the legacy usage stats directory or not
+    // STOPSHIP: b/138323140 this should be false on launch
+    private static final boolean KEEP_LEGACY_DIR = true;
+
     private static final char TOKEN_DELIMITER = '/';
 
     // Handler message types.
@@ -119,6 +140,7 @@
     static final int MSG_REMOVE_USER = 2;
     static final int MSG_UID_STATE_CHANGED = 3;
     static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
+    static final int MSG_UNLOCKED_USER = 5;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -132,10 +154,8 @@
     DevicePolicyManagerInternal mDpmInternal;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
+    private final SparseBooleanArray mUserUnlockedStates = new SparseBooleanArray();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
-    private File mUsageStatsDir;
-    long mRealTimeSnapshot;
-    long mSystemTimeSnapshot;
     int mUsageSource;
 
     /** Manages the standby state of apps. */
@@ -144,6 +164,8 @@
     /** Manages app time limit observers */
     AppTimeLimitController mAppTimeLimit;
 
+    // A map maintaining a queue of events to be reported per user.
+    private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
     final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
     final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
 
@@ -165,7 +187,7 @@
                             SystemClock.elapsedRealtime());
                     event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
                     event.mPackage = packageName;
-                    mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+                    reportEventOrAddToQueue(userId, event);
                 }
 
                 @Override
@@ -223,30 +245,14 @@
                 }, mHandler.getLooper());
 
         mAppStandby.addListener(mStandbyChangeListener);
-        File systemDataDir = new File(Environment.getDataDirectory(), "system");
-        mUsageStatsDir = new File(systemDataDir, "usagestats");
-        mUsageStatsDir.mkdirs();
-        if (!mUsageStatsDir.exists()) {
-            throw new IllegalStateException("Usage stats directory does not exist: "
-                    + mUsageStatsDir.getAbsolutePath());
-        }
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
-        synchronized (mLock) {
-            cleanUpRemovedUsersLocked();
-        }
-
-        mRealTimeSnapshot = SystemClock.elapsedRealtime();
-        mSystemTimeSnapshot = System.currentTimeMillis();
-
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
-        // Make sure we initialize the data, in case job scheduler needs it early.
-        getUserDataAndInitializeIfNeededLocked(UserHandle.USER_SYSTEM, mSystemTimeSnapshot);
     }
 
     @Override
@@ -275,6 +281,89 @@
         }
     }
 
+    @Override
+    public void onStartUser(UserInfo userInfo) {
+        // Create an entry in the user state map to indicate that the user has been started but
+        // not necessarily unlocked. This will ensure that reported events are flushed to disk
+        // event if the user is never unlocked (following the logic in #flushToDiskLocked)
+        mUserState.put(userInfo.id, null);
+        super.onStartUser(userInfo);
+    }
+
+    @Override
+    public void onUnlockUser(@NonNull UserInfo userInfo) {
+        mHandler.obtainMessage(MSG_UNLOCKED_USER, userInfo.id, 0).sendToTarget();
+        super.onUnlockUser(userInfo);
+    }
+
+    @Override
+    public void onStopUser(@NonNull UserInfo userInfo) {
+        synchronized (mLock) {
+            // User was started but never unlocked so no need to report a user stopped event
+            if (!mUserUnlockedStates.get(userInfo.id)) {
+                persistPendingEventsLocked(userInfo.id);
+                super.onStopUser(userInfo);
+                return;
+            }
+
+            // Report a user stopped event before persisting all stats to disk via the user service
+            final Event event = new Event(USER_STOPPED, SystemClock.elapsedRealtime());
+            event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
+            reportEvent(event, userInfo.id);
+            final UserUsageStatsService userService = mUserState.get(userInfo.id);
+            if (userService != null) {
+                userService.userStopped();
+            }
+            mUserUnlockedStates.put(userInfo.id, false);
+            mUserState.put(userInfo.id, null); // release the service (mainly for GC)
+        }
+        super.onStopUser(userInfo);
+    }
+
+    private void onUserUnlocked(int userId) {
+        synchronized (mLock) {
+            // Create a user unlocked event to report
+            final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
+            unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
+
+            migrateStatsToSystemCeIfNeededLocked(userId);
+
+            // Read pending reported events from disk and merge them with those stored in memory
+            final LinkedList<Event> pendingEvents = new LinkedList<>();
+            loadPendingEventsLocked(userId, pendingEvents);
+            final LinkedList<Event> eventsInMem = mReportedEvents.get(userId);
+            if (eventsInMem != null) {
+                pendingEvents.addAll(eventsInMem);
+            }
+            boolean needToFlush = !pendingEvents.isEmpty();
+
+            mUserUnlockedStates.put(userId, true);
+            final UserUsageStatsService userService = getUserDataAndInitializeIfNeededLocked(
+                    userId, System.currentTimeMillis());
+            if (userService == null) {
+                Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
+                return;
+            }
+            userService.userUnlocked(System.currentTimeMillis());
+            // Process all the pending reported events
+            while (pendingEvents.peek() != null) {
+                reportEvent(pendingEvents.poll(), userId);
+            }
+            reportEvent(unlockEvent, userId);
+
+            // Remove all the stats stored in memory and in system DE.
+            mReportedEvents.remove(userId);
+            deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
+            // Force a flush to disk for the current user to ensure important events are persisted.
+            // Note: there is a very very small chance that the system crashes between deleting
+            // the stats above from DE and persisting them to CE here in which case we will lose
+            // those events that were in memory and deleted from DE. (b/139836090)
+            if (needToFlush) {
+                userService.persistActiveStats();
+            }
+        }
+    }
+
     private DevicePolicyManagerInternal getDpmInternal() {
         if (mDpmInternal == null) {
             mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -350,33 +439,6 @@
         return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
     }
 
-    private void cleanUpRemovedUsersLocked() {
-        final List<UserInfo> users = mUserManager.getUsers(true);
-        if (users == null || users.size() == 0) {
-            throw new IllegalStateException("There can't be no users");
-        }
-
-        ArraySet<String> toDelete = new ArraySet<>();
-        String[] fileNames = mUsageStatsDir.list();
-        if (fileNames == null) {
-            // No users to delete.
-            return;
-        }
-
-        toDelete.addAll(Arrays.asList(fileNames));
-
-        final int userCount = users.size();
-        for (int i = 0; i < userCount; i++) {
-            final UserInfo userInfo = users.get(i);
-            toDelete.remove(Integer.toString(userInfo.id));
-        }
-
-        final int deleteCount = toDelete.size();
-        for (int i = 0; i < deleteCount; i++) {
-            deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i)));
-        }
-    }
-
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -385,7 +447,7 @@
             }
         }
 
-        if (!f.delete()) {
+        if (f.exists() && !f.delete()) {
             Slog.e(TAG, "Failed to delete " + f);
         }
     }
@@ -394,43 +456,125 @@
             long currentTimeMillis) {
         UserUsageStatsService service = mUserState.get(userId);
         if (service == null) {
-            service = new UserUsageStatsService(getContext(), userId,
-                    new File(mUsageStatsDir, Integer.toString(userId)), this);
-            service.init(currentTimeMillis);
+            final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
+                    "usagestats");
+            service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this);
+            if (mUserUnlockedStates.get(userId)) {
+                try {
+                    service.init(currentTimeMillis);
+                } catch (Exception e) {
+                    if (mUserManager.isUserUnlocked(userId)) {
+                        throw e; // rethrow exception - user is unlocked
+                    } else {
+                        Slog.w(TAG, "Attempted to initialize service for "
+                                + "stopped or removed user " + userId);
+                        return null;
+                    }
+                }
+            }
             mUserState.put(userId, service);
         }
         return service;
     }
 
-    /**
-     * This should be the only way to get the time from the system.
-     */
-    private long checkAndGetTimeLocked() {
-        final long actualSystemTime = System.currentTimeMillis();
-        final long actualRealtime = SystemClock.elapsedRealtime();
-        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
-        final long diffSystemTime = actualSystemTime - expectedSystemTime;
-        if (Math.abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS
-                && ENABLE_TIME_CHANGE_CORRECTION) {
-            // The time has changed.
-            Slog.i(TAG, "Time changed in UsageStats by " + (diffSystemTime / 1000) + " seconds");
-            final int userCount = mUserState.size();
-            for (int i = 0; i < userCount; i++) {
-                final UserUsageStatsService service = mUserState.valueAt(i);
-                service.onTimeChanged(expectedSystemTime, actualSystemTime);
-            }
-            mRealTimeSnapshot = actualRealtime;
-            mSystemTimeSnapshot = actualSystemTime;
+    private void migrateStatsToSystemCeIfNeededLocked(int userId) {
+        final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
+                "usagestats");
+        if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) {
+            throw new IllegalStateException("Usage stats directory does not exist: "
+                    + usageStatsDir.getAbsolutePath());
         }
-        return actualSystemTime;
+        // Check if the migrated status file exists - if not, migrate usage stats.
+        final File migrated = new File(usageStatsDir, "migrated");
+        if (migrated.exists()) {
+            try (BufferedReader reader = new BufferedReader(new FileReader(migrated))) {
+                final int previousVersion = Integer.parseInt(reader.readLine());
+                // UsageStatsDatabase.BACKUP_VERSION was 4 when usage stats were migrated to CE.
+                if (previousVersion >= 4) {
+                    deleteLegacyDir(userId);
+                    return;
+                }
+                // If migration logic needs to be changed in a future version, do it here.
+            } catch (NumberFormatException | IOException e) {
+                Slog.e(TAG, "Failed to read migration status file, possibly corrupted.");
+                deleteRecursively(usageStatsDir);
+                if (usageStatsDir.exists()) {
+                    Slog.e(TAG, "Unable to delete usage stats CE directory.");
+                    throw new RuntimeException(e);
+                } else {
+                    // Make the directory again since previous migration was not complete
+                    if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) {
+                        throw new IllegalStateException("Usage stats directory does not exist: "
+                                + usageStatsDir.getAbsolutePath());
+                    }
+                }
+            }
+        }
+
+        Slog.i(TAG, "Starting migration to system CE for user " + userId);
+        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
+        if (legacyUserDir.exists()) {
+            copyRecursively(usageStatsDir, legacyUserDir);
+        }
+        // Create a status file to indicate that the migration to CE has been completed.
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(migrated))) {
+            writer.write(Integer.toString(UsageStatsDatabase.BACKUP_VERSION));
+            writer.write("\n");
+            writer.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write migrated status file");
+            throw new RuntimeException(e);
+        }
+        Slog.i(TAG, "Finished migration to system CE for user " + userId);
+
+        // Migration was successful - delete the legacy directory
+        deleteLegacyDir(userId);
     }
 
-    /**
-     * Assuming the event's timestamp is measured in milliseconds since boot,
-     * convert it to a system wall time.
-     */
-    private void convertToSystemTimeLocked(Event event) {
-        event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
+    private static void copyRecursively(final File parent, File f) {
+        final File[] files = f.listFiles();
+        if (files == null) {
+            try {
+                Files.copy(f.toPath(), new File(parent, f.getName()).toPath(),
+                        StandardCopyOption.REPLACE_EXISTING);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to move usage stats file : " + f.toString());
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+
+        for (int i = files.length - 1; i >= 0; i--) {
+            File newParent = parent;
+            if (files[i].isDirectory()) {
+                newParent = new File(parent, files[i].getName());
+                final boolean mkdirSuccess = newParent.mkdirs();
+                if (!mkdirSuccess && !newParent.exists()) {
+                    throw new IllegalStateException(
+                            "Failed to create usage stats directory during migration: "
+                            + newParent.getAbsolutePath());
+                }
+            }
+            copyRecursively(newParent, files[i]);
+        }
+    }
+
+    private void deleteLegacyDir(int userId) {
+        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
+        if (!KEEP_LEGACY_DIR) {
+            deleteRecursively(legacyUserDir);
+            if (legacyUserDir.exists()) {
+                Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats "
+                        + "dir for user " + userId);
+            }
+            // If all users have been migrated, delete the parent legacy usage stats directory
+            if (USAGE_STATS_LEGACY_DIR.list() != null
+                    && USAGE_STATS_LEGACY_DIR.list().length == 0) {
+                if (!USAGE_STATS_LEGACY_DIR.delete()) {
+                    Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats dir");
+                }
+            }
+        }
     }
 
     /**
@@ -463,14 +607,97 @@
         mHandler.sendEmptyMessage(MSG_FLUSH_TO_DISK);
     }
 
+    private void loadPendingEventsLocked(int userId, LinkedList<Event> pendingEvents) {
+        final File usageStatsDeDir = new File(Environment.getDataSystemDeDirectory(userId),
+                "usagestats");
+        final File[] pendingEventsFiles = usageStatsDeDir.listFiles();
+        if (pendingEventsFiles == null || pendingEventsFiles.length == 0) {
+            return;
+        }
+        Arrays.sort(pendingEventsFiles);
+
+        for (int i = 0; i < pendingEventsFiles.length; i++) {
+            final AtomicFile af = new AtomicFile(pendingEventsFiles[i]);
+            try {
+                try (FileInputStream in = af.openRead()) {
+                    UsageStatsProto.readPendingEvents(in, pendingEvents);
+                }
+            } catch (IOException e) {
+                // Even if one file read fails, exit here to keep all events in order on disk -
+                // they will be read and processed the next time user is unlocked.
+                Slog.e(TAG, "Could not read " + pendingEventsFiles[i] + " for user " + userId);
+                pendingEvents.clear();
+                return;
+            }
+        }
+    }
+
+    private void persistPendingEventsLocked(int userId) {
+        final LinkedList<Event> pendingEvents = mReportedEvents.get(userId);
+        if (pendingEvents == null || pendingEvents.isEmpty()) {
+            return;
+        }
+
+        final File usageStatsDeDir = new File(Environment.getDataSystemDeDirectory(userId),
+                "usagestats");
+        if (!usageStatsDeDir.mkdirs() && !usageStatsDeDir.exists()) {
+            throw new IllegalStateException("Usage stats DE directory does not exist: "
+                    + usageStatsDeDir.getAbsolutePath());
+        }
+        final File pendingEventsFile = new File(usageStatsDeDir,
+                "pendingevents_" + System.currentTimeMillis());
+        final AtomicFile af = new AtomicFile(pendingEventsFile);
+        FileOutputStream fos = null;
+        try {
+            fos = af.startWrite();
+            UsageStatsProto.writePendingEvents(fos, pendingEvents);
+            af.finishWrite(fos);
+            fos = null;
+            pendingEvents.clear();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write " + pendingEventsFile.getAbsolutePath()
+                    + " for user " + userId);
+        } finally {
+            af.failWrite(fos); // when fos is null (successful write), this will no-op
+        }
+    }
+
+    private void reportEventOrAddToQueue(int userId, Event event) {
+        synchronized (mLock) {
+            if (mUserUnlockedStates.get(userId)) {
+                mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+                return;
+            }
+
+            final LinkedList<Event> events = mReportedEvents.get(userId, new LinkedList<>());
+            events.add(event);
+            if (mReportedEvents.get(userId) == null) {
+                mReportedEvents.put(userId, events);
+            }
+            if (events.size() == 1) {
+                // Every time a file is persisted to disk, mReportedEvents is cleared for this user
+                // so trigger a flush to disk every time the first event has been added.
+                mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
+            }
+        }
+    }
+
     /**
      * Called by the Binder stub.
      */
     void reportEvent(Event event, int userId) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
+            // This should never be called directly when the user is locked
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.wtf(TAG, "Failed to report event for locked user " + userId
+                        + " (" + event.mPackage + "/" + event.mClass
+                        + " eventType:" + event.mEventType
+                        + " instanceId:" + event.mInstanceId + ")");
+                return;
+            }
+
+            final long timeNow = System.currentTimeMillis();
             final long elapsedRealtime = SystemClock.elapsedRealtime();
-            convertToSystemTimeLocked(event);
 
             if (event.mPackage != null
                     && mPackageManagerInternal.isPackageEphemeral(userId, event.mPackage)) {
@@ -566,6 +793,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+            if (service == null) {
+                return; // user was stopped or removed
+            }
             service.reportEvent(event);
 
             mAppStandby.reportEvent(event, elapsedRealtime, userId);
@@ -581,7 +811,7 @@
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
                 Event copy = new Event(event);
-                reportEvent(copy, mUserState.keyAt(i));
+                reportEventOrAddToQueue(mUserState.keyAt(i), copy);
             }
         }
     }
@@ -597,6 +827,7 @@
             // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
             // EventList.
             Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
+            event.mPackage = DEVICE_EVENT_PACKAGE_NAME;
             reportEventToAllUserId(event);
             flushToDiskLocked();
         }
@@ -611,7 +842,6 @@
             mUserState.remove(userId);
             mAppStandby.onUserRemoved(userId);
             mAppTimeLimit.onUserRemoved(userId);
-            cleanUpRemovedUsersLocked();
         }
     }
 
@@ -621,13 +851,16 @@
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
             boolean obfuscateInstantApps) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query usage stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
             if (list == null) {
                 return null;
@@ -643,7 +876,6 @@
                     }
                 }
             }
-
             return list;
         }
     }
@@ -654,13 +886,16 @@
     List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query configuration stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryConfigurationStats(bucketType, beginTime, endTime);
         }
     }
@@ -671,13 +906,16 @@
     List<EventStats> queryEventStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query event stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventStats(bucketType, beginTime, endTime);
         }
     }
@@ -688,13 +926,16 @@
     UsageEvents queryEvents(int userId, long beginTime, long endTime,
             boolean shouldObfuscateInstantApps) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query events for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
         }
     }
@@ -705,21 +946,20 @@
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
             String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query package events for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
 
-    private static boolean validRange(long currentTime, long beginTime, long endTime) {
-        return beginTime <= currentTime && beginTime < endTime;
-    }
-
     private String buildFullToken(String packageName, String token) {
         final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
         sb.append(packageName);
@@ -731,9 +971,14 @@
     private void flushToDiskLocked() {
         final int userCount = mUserState.size();
         for (int i = 0; i < userCount; i++) {
-            UserUsageStatsService service = mUserState.valueAt(i);
+            final int userId = mUserState.keyAt(i);
+            if (!mUserUnlockedStates.get(userId)) {
+                persistPendingEventsLocked(userId);
+                continue;
+            }
+            UserUsageStatsService service = mUserState.get(userId);
             service.persistActiveStats();
-            mAppStandby.flushToDisk(mUserState.keyAt(i));
+            mAppStandby.flushToDisk(userId);
         }
         mAppStandby.flushDurationsToDisk();
 
@@ -833,6 +1078,18 @@
                     } else if ("appstandby".equals(arg)) {
                         mAppStandby.dumpState(args, pw);
                         return;
+                    } else if ("stats-directory".equals(arg)) {
+                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                        final int userId;
+                        try {
+                            userId = Integer.valueOf(args[i + 1]);
+                        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+                            ipw.println("invalid user specified.");
+                            return;
+                        }
+                        ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
+                                "usagestats").getAbsolutePath());
+                        return;
                     } else if (arg != null && !arg.startsWith("-")) {
                         // Anything else that doesn't start with '-' is a pkg to filter
                         pkg = arg;
@@ -887,7 +1144,17 @@
                 case MSG_FLUSH_TO_DISK:
                     flushToDisk();
                     break;
-
+                case MSG_UNLOCKED_USER:
+                    try {
+                        onUserUnlocked(msg.arg1);
+                    } catch (Exception e) {
+                        if (mUserManager.isUserUnlocked(msg.arg1)) {
+                            throw e; // rethrow exception - user is unlocked
+                        } else {
+                            Slog.w(TAG, "Attempted to unlock stopped or removed user " + msg.arg1);
+                        }
+                    }
+                    break;
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
                     break;
@@ -1368,7 +1635,7 @@
             event.mAction = action;
             event.mContentType = contentType;
             event.mContentAnnotations = annotations;
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1640,7 +1907,7 @@
                 event.mTaskRootPackage = taskRoot.getPackageName();
                 event.mTaskRootClass = taskRoot.getClassName();
             }
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1652,7 +1919,7 @@
 
             Event event = new Event(eventType, SystemClock.elapsedRealtime());
             event.mPackage = packageName;
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1665,7 +1932,7 @@
             Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime());
             event.mPackage = "android";
             event.mConfiguration = new Configuration(config);
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1679,7 +1946,7 @@
             Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mNotificationChannelId = channelId.intern();
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1692,7 +1959,7 @@
             Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mShortcutId = shortcutId.intern();
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1749,11 +2016,19 @@
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
-            // Check to ensure that only user 0's data is b/r for now
             synchronized (mLock) {
+                if (!mUserUnlockedStates.get(user)) {
+                    Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+                    return null;
+                }
+
+                // Check to ensure that only user 0's data is b/r for now
                 if (user == UserHandle.USER_SYSTEM) {
-                    final UserUsageStatsService userStats =
-                            getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
+                    final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
+                            user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return null; // user was stopped or removed
+                    }
                     return userStats.getBackupPayload(key);
                 } else {
                     return null;
@@ -1764,9 +2039,17 @@
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
             synchronized (mLock) {
+                if (!mUserUnlockedStates.get(user)) {
+                    Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
+                    return;
+                }
+
                 if (user == UserHandle.USER_SYSTEM) {
-                    final UserUsageStatsService userStats =
-                            getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
+                    final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
+                            user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return; // user was stopped or removed
+                    }
                     userStats.applyRestoredPayload(key, payload);
                 }
             }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 11c0e4a..1560b9e 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -76,6 +76,8 @@
     private final String mLogPrefix;
     private String mLastBackgroundedPackage;
     private final int mUserId;
+    private long mRealTimeSnapshot;
+    private long mSystemTimeSnapshot;
 
     private static final long[] INTERVAL_LENGTH = new long[] {
             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
@@ -101,6 +103,8 @@
         mListener = listener;
         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
         mUserId = userId;
+        mRealTimeSnapshot = SystemClock.elapsedRealtime();
+        mSystemTimeSnapshot = System.currentTimeMillis();
     }
 
     void init(final long currentTimeMillis) {
@@ -156,12 +160,50 @@
         }
     }
 
-    void onTimeChanged(long oldTime, long newTime) {
+    void userUnlocked(long currentTimeMillis) {
+        init(currentTimeMillis);
+    }
+
+    void userStopped() {
+        // Flush events to disk immediately to guarantee persistence.
+        persistActiveStats();
+    }
+
+    private void onTimeChanged(long oldTime, long newTime) {
         persistActiveStats();
         mDatabase.onTimeChanged(newTime - oldTime);
         loadActiveStats(newTime);
     }
 
+    /**
+     * This should be the only way to get the time from the system.
+     */
+    private long checkAndGetTimeLocked() {
+        final long actualSystemTime = System.currentTimeMillis();
+        if (!UsageStatsService.ENABLE_TIME_CHANGE_CORRECTION) {
+            return actualSystemTime;
+        }
+        final long actualRealtime = SystemClock.elapsedRealtime();
+        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+        final long diffSystemTime = actualSystemTime - expectedSystemTime;
+        if (Math.abs(diffSystemTime) > UsageStatsService.TIME_CHANGE_THRESHOLD_MILLIS) {
+            // The time has changed.
+            Slog.i(TAG, mLogPrefix + "Time changed in by " + (diffSystemTime / 1000) + " seconds");
+            onTimeChanged(expectedSystemTime, actualSystemTime);
+            mRealTimeSnapshot = actualRealtime;
+            mSystemTimeSnapshot = actualSystemTime;
+        }
+        return actualSystemTime;
+    }
+
+    /**
+     * Assuming the event's timestamp is measured in milliseconds since boot,
+     * convert it to a system wall time.
+     */
+    private void convertToSystemTimeLocked(Event event) {
+        event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
+    }
+
     void reportEvent(Event event) {
         if (DEBUG) {
             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
@@ -169,6 +211,9 @@
                     + eventToString(event.mEventType));
         }
 
+        checkAndGetTimeLocked();
+        convertToSystemTimeLocked(event);
+
         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
             // Need to rollover
             rolloverStats(event.mTimeStamp);
@@ -289,6 +334,10 @@
                 }
             };
 
+    private static boolean validRange(long currentTime, long beginTime, long endTime) {
+        return beginTime <= currentTime && beginTime < endTime;
+    }
+
     /**
      * Generic query method that selects the appropriate IntervalStats for the specified time range
      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
@@ -361,19 +410,31 @@
     }
 
     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
     }
 
     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
     }
 
     List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
     }
 
     UsageEvents queryEvents(final long beginTime, final long endTime,
                             boolean obfuscateInstantApps) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         final ArraySet<String> names = new ArraySet<>();
         List<Event> results = queryStats(INTERVAL_DAILY,
                 beginTime, endTime, new StatCombiner<Event>() {
@@ -419,6 +480,9 @@
 
     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
             final String packageName, boolean includeTaskRoot) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         final ArraySet<String> names = new ArraySet<>();
         names.add(packageName);
         final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -1021,10 +1085,12 @@
     }
 
     byte[] getBackupPayload(String key){
+        checkAndGetTimeLocked();
         return mDatabase.getBackupPayload(key);
     }
 
     void applyRestoredPayload(String key, byte[] payload){
+        checkAndGetTimeLocked();
         mDatabase.applyRestoredPayload(key, payload);
     }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbPermissionManager.java b/services/usb/java/com/android/server/usb/UsbPermissionManager.java
index 14c7c7c..ef9ee73 100644
--- a/services/usb/java/com/android/server/usb/UsbPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPermissionManager.java
@@ -60,6 +60,10 @@
         }
     }
 
+    @NonNull UsbUserPermissionManager getPermissionsForUser(@NonNull UserHandle user) {
+        return getPermissionsForUser(user.getIdentifier());
+    }
+
     void remove(@NonNull UserHandle userToRemove) {
         synchronized (mPermissionsByUser) {
             mPermissionsByUser.remove(userToRemove.getIdentifier());
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 5e136bb..d7b6b5d 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -46,6 +46,8 @@
 import android.service.usb.UsbSettingsAccessoryPreferenceProto;
 import android.service.usb.UsbSettingsDevicePreferenceProto;
 import android.service.usb.UserPackageProto;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
@@ -70,6 +72,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.net.ProtocolException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -102,10 +105,20 @@
     @GuardedBy("mLock")
     private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>();
 
+    /** Maps DeviceFilter to set of UserPackages not to ask for launch preference anymore */
+    @GuardedBy("mLock")
+    private final ArrayMap<DeviceFilter, ArraySet<UserPackage>> mDevicePreferenceDeniedMap =
+            new ArrayMap<>();
+
     /** Maps AccessoryFilter to user preferred application package */
     @GuardedBy("mLock")
     private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>();
 
+    /** Maps AccessoryFilter to set of UserPackages not to ask for launch preference anymore */
+    @GuardedBy("mLock")
+    private final ArrayMap<AccessoryFilter, ArraySet<UserPackage>> mAccessoryPreferenceDeniedMap =
+            new ArrayMap<>();
+
     private final Object mLock = new Object();
 
     /**
@@ -248,11 +261,11 @@
     }
 
     /**
-     * Remove all defaults for a user.
+     * Remove all defaults and denied packages for a user.
      *
-     * @param userToRemove The user the defaults belong to.
+     * @param userToRemove The user
      */
-    void removeAllDefaultsForUser(@NonNull UserHandle userToRemove) {
+    void removeUser(@NonNull UserHandle userToRemove) {
         synchronized (mLock) {
             boolean needToPersist = false;
             Iterator<Map.Entry<DeviceFilter, UserPackage>> devicePreferenceIt = mDevicePreferenceMap
@@ -277,6 +290,28 @@
                 }
             }
 
+            int numEntries = mDevicePreferenceDeniedMap.size();
+            for (int i = 0; i < numEntries; i++) {
+                ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.valueAt(i);
+                for (int j = userPackages.size() - 1; j >= 0; j--) {
+                    if (userPackages.valueAt(j).user.equals(userToRemove)) {
+                        userPackages.removeAt(j);
+                        needToPersist = true;
+                    }
+                }
+            }
+
+            numEntries = mAccessoryPreferenceDeniedMap.size();
+            for (int i = 0; i < numEntries; i++) {
+                ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.valueAt(i);
+                for (int j = userPackages.size() - 1; j >= 0; j--) {
+                    if (userPackages.valueAt(j).user.equals(userToRemove)) {
+                        userPackages.removeAt(j);
+                        needToPersist = true;
+                    }
+                }
+            }
+
             if (needToPersist) {
                 scheduleWriteSettingsLocked();
             }
@@ -284,7 +319,7 @@
     }
 
     private void readPreference(XmlPullParser parser)
-            throws XmlPullParserException, IOException {
+            throws IOException, XmlPullParserException {
         String packageName = null;
 
         // If not set, assume it to be the parent profile
@@ -317,9 +352,70 @@
         XmlUtils.nextElement(parser);
     }
 
+    private void readPreferenceDeniedList(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        if (!XmlUtils.nextElementWithin(parser, outerDepth)) {
+            return;
+        }
+
+        if ("usb-device".equals(parser.getName())) {
+            DeviceFilter filter = DeviceFilter.read(parser);
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if ("user-package".equals(parser.getName())) {
+                    try {
+                        int userId = XmlUtils.readIntAttribute(parser, "user");
+
+                        String packageName = XmlUtils.readStringAttribute(parser, "package");
+                        if (packageName == null) {
+                            Slog.e(TAG, "Unable to parse package name");
+                        }
+
+                        ArraySet<UserPackage> set = mDevicePreferenceDeniedMap.get(filter);
+                        if (set == null) {
+                            set = new ArraySet<>();
+                            mDevicePreferenceDeniedMap.put(filter, set);
+                        }
+                        set.add(new UserPackage(packageName, UserHandle.of(userId)));
+                    } catch (ProtocolException e) {
+                        Slog.e(TAG, "Unable to parse user id", e);
+                    }
+                }
+            }
+        } else if ("usb-accessory".equals(parser.getName())) {
+            AccessoryFilter filter = AccessoryFilter.read(parser);
+
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if ("user-package".equals(parser.getName())) {
+                    try {
+                        int userId = XmlUtils.readIntAttribute(parser, "user");
+
+                        String packageName = XmlUtils.readStringAttribute(parser, "package");
+                        if (packageName == null) {
+                            Slog.e(TAG, "Unable to parse package name");
+                        }
+
+                        ArraySet<UserPackage> set = mAccessoryPreferenceDeniedMap.get(filter);
+                        if (set == null) {
+                            set = new ArraySet<>();
+                            mAccessoryPreferenceDeniedMap.put(filter, set);
+                        }
+                        set.add(new UserPackage(packageName, UserHandle.of(userId)));
+                    } catch (ProtocolException e) {
+                        Slog.e(TAG, "Unable to parse user id", e);
+                    }
+                }
+            }
+        }
+
+        while (parser.getDepth() > outerDepth) {
+            parser.nextTag(); // ignore unknown tags
+        }
+    }
+
     /**
      * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
-     * Should only by called by owner.
+     * Should only be called by owner.
      */
     @GuardedBy("mLock")
     private void upgradeSingleUserLocked() {
@@ -373,6 +469,8 @@
                 String tagName = parser.getName();
                 if ("preference".equals(tagName)) {
                     readPreference(parser);
+                } else if ("preference-denied-list".equals(tagName)) {
+                    readPreferenceDeniedList(parser);
                 } else {
                     XmlUtils.nextElement(parser);
                 }
@@ -436,6 +534,46 @@
                         serializer.endTag(null, "preference");
                     }
 
+                    int numEntries = mDevicePreferenceDeniedMap.size();
+                    for (int i = 0; i < numEntries; i++) {
+                        DeviceFilter filter = mDevicePreferenceDeniedMap.keyAt(i);
+                        ArraySet<UserPackage> userPackageSet = mDevicePreferenceDeniedMap
+                                .valueAt(i);
+                        serializer.startTag(null, "preference-denied-list");
+                        filter.write(serializer);
+
+                        int numUserPackages = userPackageSet.size();
+                        for (int j = 0; j < numUserPackages; j++) {
+                            UserPackage userPackage = userPackageSet.valueAt(j);
+                            serializer.startTag(null, "user-package");
+                            serializer.attribute(null, "user",
+                                    String.valueOf(getSerial(userPackage.user)));
+                            serializer.attribute(null, "package", userPackage.packageName);
+                            serializer.endTag(null, "user-package");
+                        }
+                        serializer.endTag(null, "preference-denied-list");
+                    }
+
+                    numEntries = mAccessoryPreferenceDeniedMap.size();
+                    for (int i = 0; i < numEntries; i++) {
+                        AccessoryFilter filter = mAccessoryPreferenceDeniedMap.keyAt(i);
+                        ArraySet<UserPackage> userPackageSet =
+                                mAccessoryPreferenceDeniedMap.valueAt(i);
+                        serializer.startTag(null, "preference-denied-list");
+                        filter.write(serializer);
+
+                        int numUserPackages = userPackageSet.size();
+                        for (int j = 0; j < numUserPackages; j++) {
+                            UserPackage userPackage = userPackageSet.valueAt(j);
+                            serializer.startTag(null, "user-package");
+                            serializer.attribute(null, "user",
+                                    String.valueOf(getSerial(userPackage.user)));
+                            serializer.attribute(null, "package", userPackage.packageName);
+                            serializer.endTag(null, "user-package");
+                        }
+                        serializer.endTag(null, "preference-denied-list");
+                    }
+
                     serializer.endTag(null, "settings");
                     serializer.endDocument();
 
@@ -834,6 +972,25 @@
     private void resolveActivity(@NonNull Intent intent, @NonNull ArrayList<ResolveInfo> matches,
             @Nullable ActivityInfo defaultActivity, @Nullable UsbDevice device,
             @Nullable UsbAccessory accessory) {
+        // Remove all matches which are on the denied list
+        ArraySet deniedPackages = null;
+        if (device != null) {
+            deniedPackages = mDevicePreferenceDeniedMap.get(new DeviceFilter(device));
+        } else if (accessory != null) {
+            deniedPackages = mAccessoryPreferenceDeniedMap.get(new AccessoryFilter(accessory));
+        }
+        if (deniedPackages != null) {
+            for (int i = matches.size() - 1; i >= 0; i--) {
+                ResolveInfo match = matches.get(i);
+                String packageName = match.activityInfo.packageName;
+                UserHandle user = UserHandle
+                        .getUserHandleForUid(match.activityInfo.applicationInfo.uid);
+                if (deniedPackages.contains(new UserPackage(packageName, user))) {
+                    matches.remove(i);
+                }
+            }
+        }
+
         // don't show the resolver activity if there are no choices available
         if (matches.size() == 0) {
             if (accessory != null) {
@@ -1076,6 +1233,156 @@
     }
 
     /**
+     * Add package to the denied for handling a device
+     *
+     * @param device the device to add to the denied
+     * @param packageNames the packages to not become handler
+     * @param user the user
+     */
+    void addDevicePackagesToDenied(@NonNull UsbDevice device, @NonNull String[] packageNames,
+            @NonNull UserHandle user) {
+        if (packageNames.length == 0) {
+            return;
+        }
+        DeviceFilter filter = new DeviceFilter(device);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages;
+            if (mDevicePreferenceDeniedMap.containsKey(filter)) {
+                userPackages = mDevicePreferenceDeniedMap.get(filter);
+            } else {
+                userPackages = new ArraySet<>();
+                mDevicePreferenceDeniedMap.put(filter, userPackages);
+            }
+
+            boolean shouldWrite = false;
+            for (String packageName : packageNames) {
+                UserPackage userPackage = new UserPackage(packageName, user);
+                if (!userPackages.contains(userPackage)) {
+                    userPackages.add(userPackage);
+                    shouldWrite = true;
+                }
+            }
+
+            if (shouldWrite) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+    }
+
+    /**
+     * Add package to the denied for handling a accessory
+     *
+     * @param accessory the accessory to add to the denied
+     * @param packageNames the packages to not become handler
+     * @param user the user
+     */
+    void addAccessoryPackagesToDenied(@NonNull UsbAccessory accessory,
+            @NonNull String[] packageNames, @NonNull UserHandle user) {
+        if (packageNames.length == 0) {
+            return;
+        }
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages;
+            if (mAccessoryPreferenceDeniedMap.containsKey(filter)) {
+                userPackages = mAccessoryPreferenceDeniedMap.get(filter);
+            } else {
+                userPackages = new ArraySet<>();
+                mAccessoryPreferenceDeniedMap.put(filter, userPackages);
+            }
+
+            boolean shouldWrite = false;
+            for (String packageName : packageNames) {
+                UserPackage userPackage = new UserPackage(packageName, user);
+                if (!userPackages.contains(userPackage)) {
+                    userPackages.add(userPackage);
+                    shouldWrite = true;
+                }
+            }
+
+            if (shouldWrite) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+    }
+
+    /**
+     * Remove UserPackage from the denied for handling a device
+     *
+     * @param device the device to remove denied packages from
+     * @param packageName the packages to remove
+     * @param user the user
+     */
+    void removeDevicePackagesFromDenied(@NonNull UsbDevice device, @NonNull String[] packageNames,
+            @NonNull UserHandle user) {
+        DeviceFilter filter = new DeviceFilter(device);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.get(filter);
+
+            if (userPackages != null) {
+                boolean shouldWrite = false;
+                for (String packageName : packageNames) {
+                    UserPackage userPackage = new UserPackage(packageName, user);
+
+                    if (userPackages.contains(userPackage)) {
+                        userPackages.remove(userPackage);
+                        shouldWrite = true;
+
+                        if (userPackages.size() == 0) {
+                            mDevicePreferenceDeniedMap.remove(filter);
+                            break;
+                        }
+                    }
+                }
+
+                if (shouldWrite) {
+                    scheduleWriteSettingsLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove UserPackage from the denied for handling a accessory
+     *
+     * @param accessory the accessory to remove denied packages from
+     * @param packageName the packages to remove
+     * @param user the user
+     */
+    void removeAccessoryPackagesFromDenied(@NonNull UsbAccessory accessory,
+            @NonNull String[] packageNames, @NonNull UserHandle user) {
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.get(filter);
+
+            if (userPackages != null) {
+                boolean shouldWrite = false;
+                for (String packageName : packageNames) {
+                    UserPackage userPackage = new UserPackage(packageName, user);
+
+                    if (userPackages.contains(userPackage)) {
+                        userPackages.remove(userPackage);
+                        shouldWrite = true;
+
+                        if (userPackages.size() == 0) {
+                            mAccessoryPreferenceDeniedMap.remove(filter);
+                            break;
+                        }
+                    }
+                }
+
+                if (shouldWrite) {
+                    scheduleWriteSettingsLocked();
+                }
+            }
+        }
+    }
+
+    /**
      * Set a package as default handler for a accessory.
      *
      * @param accessory The accessory that should be handled by default
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index cc1490e..ce6f592 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -361,6 +361,112 @@
     }
 
     @Override
+    public void addDevicePackagesToPreferenceDenied(UsbDevice device, String[] packageNames,
+            UserHandle user) {
+        device = Preconditions.checkNotNull(device);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .addDevicePackagesToDenied(device, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void addAccessoryPackagesToPreferenceDenied(UsbAccessory accessory,
+            String[] packageNames, UserHandle user) {
+        accessory = Preconditions.checkNotNull(accessory);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .addAccessoryPackagesToDenied(accessory, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void removeDevicePackagesFromPreferenceDenied(UsbDevice device, String[] packageNames,
+            UserHandle user) {
+        device = Preconditions.checkNotNull(device);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .removeDevicePackagesFromDenied(device, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void removeAccessoryPackagesFromPreferenceDenied(UsbAccessory accessory,
+            String[] packageNames, UserHandle user) {
+        accessory = Preconditions.checkNotNull(accessory);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .removeAccessoryPackagesFromDenied(accessory, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void setDevicePersistentPermission(UsbDevice device, int uid, UserHandle user,
+            boolean shouldBeGranted) {
+        device = Preconditions.checkNotNull(device);
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mPermissionManager.getPermissionsForUser(user).setDevicePersistentPermission(device,
+                    uid, shouldBeGranted);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void setAccessoryPersistentPermission(UsbAccessory accessory, int uid,
+            UserHandle user, boolean shouldBeGranted) {
+        accessory = Preconditions.checkNotNull(accessory);
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mPermissionManager.getPermissionsForUser(user).setAccessoryPersistentPermission(
+                    accessory, uid, shouldBeGranted);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
     public boolean hasDevicePermission(UsbDevice device, String packageName) {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index fbd8782a..7b677ee 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -130,7 +130,7 @@
                 // it from all profile groups.
                 int numProfileGroups = mSettingsByProfileGroup.size();
                 for (int i = 0; i < numProfileGroups; i++) {
-                    mSettingsByProfileGroup.valueAt(i).removeAllDefaultsForUser(userToRemove);
+                    mSettingsByProfileGroup.valueAt(i).removeUser(userToRemove);
                 }
             }
         }
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index ec7567c..0cb64a3 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -24,24 +24,41 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
+import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Process;
 import android.os.UserHandle;
 import android.service.usb.UsbSettingsAccessoryPermissionProto;
 import android.service.usb.UsbSettingsDevicePermissionProto;
 import android.service.usb.UsbUserSettingsManagerProto;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 
-import java.util.HashMap;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * UsbUserPermissionManager manages usb device or accessory access permissions.
@@ -49,25 +66,45 @@
  * @hide
  */
 class UsbUserPermissionManager {
-    private static final String LOG_TAG = UsbUserPermissionManager.class.getSimpleName();
+    private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     @GuardedBy("mLock")
-    /** Temporary mapping USB device name to list of UIDs with permissions for the device*/
-    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
-            new HashMap<>();
+    /** Mapping of USB device name to list of UIDs with permissions for the device
+     * Each entry lasts until device is disconnected*/
+    private final ArrayMap<String, SparseBooleanArray> mDevicePermissionMap =
+            new ArrayMap<>();
     @GuardedBy("mLock")
-    /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory*/
-    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
-            new HashMap<>();
+    /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
+     * Each entry lasts until accessory is disconnected*/
+    private final ArrayMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
+            new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    /** Maps USB device to list of UIDs with persistent permissions for the device*/
+    private final ArrayMap<DeviceFilter, SparseBooleanArray>
+            mDevicePersistentPermissionMap = new ArrayMap<>();
+    @GuardedBy("mLock")
+    /** Maps Usb Accessory to list of UIDs with persistent permissions for the accessory*/
+    private final ArrayMap<AccessoryFilter, SparseBooleanArray>
+            mAccessoryPersistentPermissionMap = new ArrayMap<>();
 
     private final Context mContext;
     private final UserHandle mUser;
     private final UsbUserSettingsManager mUsbUserSettingsManager;
     private final boolean mDisablePermissionDialogs;
 
+    private final @NonNull AtomicFile mPermissionsFile;
+
     private final Object mLock = new Object();
 
+    /**
+     * If a async task to persist the mDevicePersistentPreferenceMap and
+     * mAccessoryPersistentPreferenceMap is currently scheduled.
+     */
+    @GuardedBy("mLock")
+    private boolean mIsCopyPermissionsScheduled;
+
     UsbUserPermissionManager(@NonNull Context context, @NonNull UserHandle user,
             @NonNull UsbUserSettingsManager usbUserSettingsManager) {
         mContext = context;
@@ -75,6 +112,14 @@
         mUsbUserSettingsManager = usbUserSettingsManager;
         mDisablePermissionDialogs = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_disableUsbPermissionDialogs);
+
+        mPermissionsFile = new AtomicFile(new File(
+                Environment.getUserSystemDirectory(user.getIdentifier()),
+                "usb_permissions.xml"), "usb-permissions");
+
+        synchronized (mLock) {
+            readPermissionsLocked();
+        }
     }
 
     /**
@@ -141,11 +186,24 @@
      * @param uid to check permission for
      * @return {@code true} if package with uid has permission
      */
-    boolean hasPermission(@NonNull UsbDevice device, int uid) {
+    boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int uid) {
+        if (isCameraDevicePresent(device)) {
+            if (!isCameraPermissionGranted(packageName, uid)) {
+                return false;
+            }
+        }
         synchronized (mLock) {
             if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                 return true;
             }
+            DeviceFilter filter = new DeviceFilter(device);
+            SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter);
+            if (permissionsForDevice != null) {
+                int idx = permissionsForDevice.indexOfKey(uid);
+                if (idx >= 0) {
+                    return permissionsForDevice.valueAt(idx);
+                }
+            }
             SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
             if (uidList == null) {
                 return false;
@@ -166,6 +224,15 @@
             if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                 return true;
             }
+            AccessoryFilter filter = new AccessoryFilter(accessory);
+            SparseBooleanArray permissionsForAccessory =
+                    mAccessoryPersistentPermissionMap.get(filter);
+            if (permissionsForAccessory != null) {
+                int idx = permissionsForAccessory.indexOfKey(uid);
+                if (idx >= 0) {
+                    return permissionsForAccessory.valueAt(idx);
+                }
+            }
             SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
             if (uidList == null) {
                 return false;
@@ -174,14 +241,236 @@
         }
     }
 
-    boolean hasPermission(UsbDevice device, String packageName, int uid) {
-        if (isCameraDevicePresent(device)) {
-            if (!isCameraPermissionGranted(packageName, uid)) {
-                return false;
+    void setDevicePersistentPermission(@NonNull UsbDevice device, int uid, boolean isGranted) {
+
+        boolean isChanged;
+        DeviceFilter filter = new DeviceFilter(device);
+        synchronized (mLock) {
+            SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter);
+            if (permissionsForDevice == null) {
+                permissionsForDevice = new SparseBooleanArray();
+                mDevicePersistentPermissionMap.put(filter, permissionsForDevice);
+            }
+            int idx = permissionsForDevice.indexOfKey(uid);
+            if (idx >= 0) {
+                isChanged = permissionsForDevice.valueAt(idx) != isGranted;
+                permissionsForDevice.setValueAt(idx, isGranted);
+            } else {
+                isChanged = true;
+                permissionsForDevice.put(uid, isGranted);
+            }
+
+            if (isChanged) {
+                scheduleWritePermissionLocked();
             }
         }
+    }
 
-        return hasPermission(device, uid);
+    void setAccessoryPersistentPermission(@NonNull UsbAccessory accessory, int uid,
+            boolean isGranted) {
+
+        boolean isChanged;
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+        synchronized (mLock) {
+            SparseBooleanArray permissionsForAccessory =
+                    mAccessoryPersistentPermissionMap.get(filter);
+            if (permissionsForAccessory == null) {
+                permissionsForAccessory = new SparseBooleanArray();
+                mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory);
+            }
+            int idx = permissionsForAccessory.indexOfKey(uid);
+            if (idx >= 0) {
+                isChanged = permissionsForAccessory.valueAt(idx) != isGranted;
+                permissionsForAccessory.setValueAt(idx, isGranted);
+            } else {
+                isChanged = true;
+                permissionsForAccessory.put(uid, isGranted);
+            }
+
+            if (isChanged) {
+                scheduleWritePermissionLocked();
+            }
+        }
+    }
+
+    private void readPermission(@NonNull XmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        int uid;
+        boolean isGranted;
+
+        try {
+            uid = XmlUtils.readIntAttribute(parser, "uid");
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "error reading usb permission uid", e);
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
+
+        // only use "true"/"false" as valid values
+        String isGrantedString = parser.getAttributeValue(null, "granted");
+        if (isGrantedString == null || !(isGrantedString.equals(Boolean.TRUE.toString())
+                || isGrantedString.equals(Boolean.FALSE.toString()))) {
+            Slog.e(TAG, "error reading usb permission granted state");
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
+        isGranted = isGrantedString.equals(Boolean.TRUE.toString());
+        XmlUtils.nextElement(parser);
+        if ("usb-device".equals(parser.getName())) {
+            DeviceFilter filter = DeviceFilter.read(parser);
+            int idx = mDevicePersistentPermissionMap.indexOfKey(filter);
+            if (idx >= 0) {
+                SparseBooleanArray permissionsForDevice =
+                        mDevicePersistentPermissionMap.valueAt(idx);
+                permissionsForDevice.put(uid, isGranted);
+            } else {
+                SparseBooleanArray permissionsForDevice = new SparseBooleanArray();
+                mDevicePersistentPermissionMap.put(filter, permissionsForDevice);
+                permissionsForDevice.put(uid, isGranted);
+            }
+        } else if ("usb-accessory".equals(parser.getName())) {
+            AccessoryFilter filter = AccessoryFilter.read(parser);
+            int idx = mAccessoryPersistentPermissionMap.indexOfKey(filter);
+            if (idx >= 0) {
+                SparseBooleanArray permissionsForAccessory =
+                        mAccessoryPersistentPermissionMap.valueAt(idx);
+                permissionsForAccessory.put(uid, isGranted);
+            } else {
+                SparseBooleanArray permissionsForAccessory = new SparseBooleanArray();
+                mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory);
+                permissionsForAccessory.put(uid, isGranted);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readPermissionsLocked() {
+        mDevicePersistentPermissionMap.clear();
+        mAccessoryPersistentPermissionMap.clear();
+
+        try (FileInputStream in = mPermissionsFile.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            XmlUtils.nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if ("permission".equals(tagName)) {
+                    readPermission(parser);
+                } else {
+                    XmlUtils.nextElement(parser);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            if (DEBUG) Slog.d(TAG, "usb permissions file not found");
+        } catch (Exception e) {
+            Slog.e(TAG, "error reading usb permissions file, deleting to start fresh", e);
+            mPermissionsFile.delete();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleWritePermissionLocked() {
+        if (mIsCopyPermissionsScheduled) {
+            return;
+        }
+        mIsCopyPermissionsScheduled = true;
+
+        AsyncTask.execute(() -> {
+            int numDevices;
+            DeviceFilter[] devices;
+            int[][] uidsForDevices;
+            boolean[][] grantedValuesForDevices;
+
+            int numAccessories;
+            AccessoryFilter[] accessories;
+            int[][] uidsForAccessories;
+            boolean[][] grantedValuesForAccessories;
+
+            synchronized (mLock) {
+                // Copy the permission state so we can write outside of lock
+                numDevices = mDevicePersistentPermissionMap.size();
+                devices = new DeviceFilter[numDevices];
+                uidsForDevices = new int[numDevices][];
+                grantedValuesForDevices = new boolean[numDevices][];
+                for (int i = 0; i < numDevices; i++) {
+                    devices[i] = new DeviceFilter(mDevicePersistentPermissionMap.keyAt(i));
+                    SparseBooleanArray permissions = mDevicePersistentPermissionMap.valueAt(i);
+                    int numPermissions = permissions.size();
+                    uidsForDevices[i] = new int[numPermissions];
+                    grantedValuesForDevices[i] = new boolean[numPermissions];
+                    for (int j = 0; j < numPermissions; j++) {
+                        uidsForDevices[i][j] = permissions.keyAt(j);
+                        grantedValuesForDevices[i][j] = permissions.valueAt(j);
+                    }
+                }
+
+                numAccessories = mAccessoryPersistentPermissionMap.size();
+                accessories = new AccessoryFilter[numAccessories];
+                uidsForAccessories = new int[numAccessories][];
+                grantedValuesForAccessories = new boolean[numAccessories][];
+                for (int i = 0; i < numAccessories; i++) {
+                    accessories[i] =
+                            new AccessoryFilter(mAccessoryPersistentPermissionMap.keyAt(i));
+                    SparseBooleanArray permissions = mAccessoryPersistentPermissionMap.valueAt(i);
+                    int numPermissions = permissions.size();
+                    uidsForAccessories[i] = new int[numPermissions];
+                    grantedValuesForAccessories[i] = new boolean[numPermissions];
+                    for (int j = 0; j < numPermissions; j++) {
+                        uidsForAccessories[i][j] = permissions.keyAt(j);
+                        grantedValuesForAccessories[i][j] = permissions.valueAt(j);
+                    }
+                }
+                mIsCopyPermissionsScheduled = false;
+            }
+
+            synchronized (mPermissionsFile) {
+                FileOutputStream out = null;
+                try {
+                    out = mPermissionsFile.startWrite();
+                    FastXmlSerializer serializer = new FastXmlSerializer();
+                    serializer.setOutput(out, StandardCharsets.UTF_8.name());
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, "permissions");
+
+                    for (int i = 0; i < numDevices; i++) {
+                        int numPermissions = uidsForDevices[i].length;
+                        for (int j = 0; j < numPermissions; j++) {
+                            serializer.startTag(null, "permission");
+                            serializer.attribute(null, "uid",
+                                    Integer.toString(uidsForDevices[i][j]));
+                            serializer.attribute(null, "granted",
+                                    Boolean.toString(grantedValuesForDevices[i][j]));
+                            devices[i].write(serializer);
+                            serializer.endTag(null, "permission");
+                        }
+                    }
+
+                    for (int i = 0; i < numAccessories; i++) {
+                        int numPermissions = uidsForAccessories[i].length;
+                        for (int j = 0; j < numPermissions; j++) {
+                            serializer.startTag(null, "permission");
+                            serializer.attribute(null, "uid",
+                                    Integer.toString(uidsForAccessories[i][j]));
+                            serializer.attribute(null, "granted",
+                                    Boolean.toString(grantedValuesForDevices[i][j]));
+                            accessories[i].write(serializer);
+                            serializer.endTag(null, "permission");
+                        }
+                    }
+
+                    serializer.endTag(null, "permissions");
+                    serializer.endDocument();
+
+                    mPermissionsFile.finishWrite(out);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to write permissions", e);
+                    if (out != null) {
+                        mPermissionsFile.failWrite(out);
+                    }
+                }
+            }
+        });
     }
 
     /**
@@ -222,7 +511,7 @@
         try {
             userContext.startActivityAsUser(intent, mUser);
         } catch (ActivityNotFoundException e) {
-            Slog.e(LOG_TAG, "unable to start UsbPermissionActivity");
+            Slog.e(TAG, "unable to start UsbPermissionActivity");
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -278,19 +567,19 @@
             ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
             // compare uid with packageName to foil apps pretending to be someone else
             if (aInfo.uid != uid) {
-                Slog.i(LOG_TAG, "Package " + packageName + " does not match caller's uid " + uid);
+                Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
                 return false;
             }
             targetSdkVersion = aInfo.targetSdkVersion;
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.i(LOG_TAG, "Package not found, likely due to invalid package name!");
+            Slog.i(TAG, "Package not found, likely due to invalid package name!");
             return false;
         }
 
         if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
             int allowed = mContext.checkCallingPermission(android.Manifest.permission.CAMERA);
             if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
-                Slog.i(LOG_TAG, "Camera permission required for USB video class devices");
+                Slog.i(TAG, "Camera permission required for USB video class devices");
                 return false;
             }
         }
@@ -342,7 +631,7 @@
             try {
                 pi.send(mContext, 0, intent);
             } catch (PendingIntent.CanceledException e) {
-                if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
             }
             return;
         }
@@ -353,7 +642,7 @@
                 try {
                     pi.send(mContext, 0, intent);
                 } catch (PendingIntent.CanceledException e) {
-                    if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                    if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
                 }
                 return;
             }
@@ -373,7 +662,7 @@
             try {
                 pi.send(mContext, 0, intent);
             } catch (PendingIntent.CanceledException e) {
-                if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
             }
             return;
         }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index de2dd10..3f2d8c8 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -41,6 +41,8 @@
                                  //     D4..0 Reserved, set to 0.
     private int mMaxPower;       // 8:1 Maximum Power Consumption in 2mA units
 
+    private boolean mBlockAudio; // leave it off for now. We be replace with a "Developer Option"
+
     private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
             new ArrayList<UsbInterfaceDescriptor>();
 
@@ -77,21 +79,35 @@
         mInterfaceDescriptors.add(interfaceDesc);
     }
 
+    private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) {
+        return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO
+                && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING;
+    }
+
     UsbConfiguration toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "  toAndroid()");
         }
+
+        // NOTE - This code running in the server process.
+        //TODO (pmclean@) - remove this
+//        int pid = android.os.Process.myPid();
+//        int uid = android.os.Process.myUid();
+//        Log.d(TAG, "  ---- pid:" + pid + " uid:" + uid);
+
         String name = parser.getDescriptorString(mConfigIndex);
         UsbConfiguration config = new
                 UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
-        UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
-        if (UsbDescriptorParser.DEBUG) {
-            Log.d(TAG, "    " + mInterfaceDescriptors.size() + " interfaces.");
+
+        ArrayList<UsbInterface> filteredInterfaces = new ArrayList<UsbInterface>();
+        for (UsbInterfaceDescriptor descriptor : mInterfaceDescriptors) {
+            if (!mBlockAudio || !isAudioInterface(descriptor)) {
+                filteredInterfaces.add(descriptor.toAndroid(parser));
+            }
         }
-        for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
-            interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
-        }
-        config.setInterfaces(interfaces);
+        UsbInterface[] interfaceArray = new UsbInterface[0];
+        interfaceArray = filteredInterfaces.toArray(interfaceArray);
+        config.setInterfaces(interfaceArray);
         return config;
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 4f62d5a..6ffbd43 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -26,7 +26,7 @@
  */
 public final class UsbDescriptorParser {
     private static final String TAG = "UsbDescriptorParser";
-    public static final boolean DEBUG = false;
+    public static final boolean DEBUG = true;
 
     private final String mDeviceAddr;
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 5eb0a2f..4d0cfea 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -31,7 +31,7 @@
     public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
     public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
     public static final int DIRECTION_OUTPUT = 0x0000;
-    public static final int DIRECTION_INPUT = (byte) 0x0080;
+    public static final int DIRECTION_INPUT = 0x0080;
 
     public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
     public static final int TRANSTYPE_CONTROL = 0x00;
@@ -85,7 +85,7 @@
     }
 
     public int getEndpointAddress() {
-        return mEndpointAddress;
+        return mEndpointAddress & MASK_ENDPOINT_ADDRESS;
     }
 
     public int getAttributes() {
@@ -108,6 +108,10 @@
         return mSyncAddress;
     }
 
+    public int getDirection() {
+        return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
+    }
+
     /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "toAndroid() type:"
@@ -137,11 +141,9 @@
 
         canvas.openList();
 
-        int address = getEndpointAddress();
         canvas.writeListItem("Address: "
-                + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
-                + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
-                == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
+                + ReportCanvas.getHexString(getEndpointAddress())
+                + (getDirection() == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
 
         int attributes = getAttributes();
         canvas.openListItem();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 1dc6069..64dbd97 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -31,7 +31,6 @@
  */
 public class UsbInterfaceDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbInterfaceDescriptor";
-
     protected int mInterfaceNumber;   // 2:1 Number of Interface
     protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
     protected byte mNumEndpoints;     // 4:1 Number of Endpoints used for this interface
@@ -73,6 +72,19 @@
         return mNumEndpoints;
     }
 
+    /**
+     * @param index Index of desired UsbEndpointDescriptor.
+     * @return the UsbEndpointDescriptor descriptor at the specified index, or
+     *  null if an invalid index.
+     */
+    public UsbEndpointDescriptor getEndpointDescriptor(int index) {
+        if (index < 0 || index >= mEndpointDescriptors.size()) {
+            return null;
+        }
+
+        return mEndpointDescriptors.get(index);
+    }
+
     public int getUsbClass() {
         return mUsbClass;
     }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 9933756..735b9a1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -325,9 +325,8 @@
                     false /* Don't notify for synchronous calls */);
 
                                 // Initialize power save, call active state monitoring logic.
-            if (status == STATUS_OK && !mRecognitionRequested) {
+            if (status == STATUS_OK) {
                 initializeTelephonyAndPowerStateListeners();
-                mRecognitionRequested = true;
             }
 
             return status;
@@ -481,6 +480,9 @@
 
         if (unloadModel && modelData.isModelLoaded()) {
             Slog.d(TAG, "Unloading previously loaded stale model.");
+            if (mModule == null) {
+                return STATUS_ERROR;
+            }
             status = mModule.unloadSoundModel(modelData.getHandle());
             MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
             if (status != SoundTrigger.STATUS_OK) {
@@ -550,6 +552,9 @@
                 }
             }
 
+            if (mModule == null) {
+                return STATUS_ERROR;
+            }
             int status = mModule.unloadSoundModel(modelData.getHandle());
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
@@ -878,6 +883,7 @@
             mContext.unregisterReceiver(mPowerSaveModeListener);
             mPowerSaveModeListener = null;
         }
+        mRecognitionRequested = false;
     }
 
     // Clears state for all models (generic and keyphrase).
@@ -924,6 +930,9 @@
     }
 
     private void initializeTelephonyAndPowerStateListeners() {
+        if (mRecognitionRequested) {
+            return;
+        }
         long token = Binder.clearCallingIdentity();
         try {
             // Get the current call state synchronously for the first recognition.
@@ -941,6 +950,8 @@
             }
             mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
                     .batterySaverEnabled;
+
+            mRecognitionRequested = true;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -987,6 +998,9 @@
         if (exception != null) {
           Slog.e(TAG, "forceStopAndUnloadModel", exception);
         }
+        if (mModule == null) {
+            return;
+        }
         if (modelData.isModelStarted()) {
             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
             if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
@@ -1093,6 +1107,13 @@
     // a recognition include: no active phone call or not being in a power save mode. Also,
     // the native service should be enabled.
     private boolean isRecognitionAllowed() {
+        // if mRecognitionRequested is false, call and power state listeners are not registered so
+        // we read current state directly from services
+        if (!mRecognitionRequested) {
+            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
+            mIsPowerSaveMode =
+                mPowerManager.getPowerSaveState(ServiceType.SOUND).batterySaverEnabled;
+        }
         return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
     }
 
@@ -1116,6 +1137,9 @@
             return STATUS_OK;
         }
 
+        if (mModule == null) {
+            return STATUS_ERROR;
+        }
         int status = mModule.startRecognition(handle, config);
         if (status != SoundTrigger.STATUS_OK) {
             Slog.w(TAG, "startRecognition failed with " + status);
@@ -1152,8 +1176,11 @@
     }
 
     private int stopRecognitionLocked(ModelData modelData, boolean notify) {
-        IRecognitionStatusCallback callback = modelData.getCallback();
+        if (mModule == null) {
+            return STATUS_ERROR;
+        }
 
+        IRecognitionStatusCallback callback = modelData.getCallback();
         // Stop recognition.
         int status = STATUS_OK;
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index a8cafb3..1dd3972 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -666,6 +666,23 @@
                 return ret;
             }
         }
+
+        @Override
+        @Nullable
+        public ModuleProperties getModuleProperties() {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return null;
+            if (DEBUG) {
+                Slog.i(TAG, "getModuleProperties()");
+            }
+
+            synchronized (mLock) {
+                ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "getModuleProperties(): " + properties.toString()));
+                return properties;
+            }
+        }
     }
 
     /**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f16dec6..0ff9273 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -194,7 +194,7 @@
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
-                                | Context.BIND_FOREGROUND_SERVICE
+                                | Context.BIND_SCHEDULE_LIKE_TOP_APP
                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                         new UserHandle(mUser));
             }
diff --git a/services/wifi/java/android/net/wifi/WifiStackClient.java b/services/wifi/java/android/net/wifi/WifiStackClient.java
index fa66e4c..64af7a8 100644
--- a/services/wifi/java/android/net/wifi/WifiStackClient.java
+++ b/services/wifi/java/android/net/wifi/WifiStackClient.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityModuleConnector;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -56,13 +57,20 @@
         public void onModuleServiceConnected(IBinder service) {
             Log.i(TAG, "Wifi stack connected");
 
-            registerWifiStackService(service);
-            IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
-            registerApiServiceAndStart(connector, Context.WIFI_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE);
+            // spin up a new thread to not block system_server main thread
+            HandlerThread thread = new HandlerThread("InitWifiServicesThread");
+            thread.start();
+            thread.getThreadHandler().post(() -> {
+                registerWifiStackService(service);
+                IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
+                registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE);
+
+                thread.quitSafely();
+            });
         }
     }
 
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a4906d7..a806320 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,11 +17,13 @@
 android_app {
     name: "startop_test_app",
     srcs: [
+        "src/CPUIntensive.java",
         "src/EmptyActivity.java",
         "src/LayoutInflationActivity.java",
         "src/ComplexLayoutInflationActivity.java",
         "src/FrameLayoutInflationActivity.java",
+        "src/SystemServerBenchmarkActivity.java",
         "src/TextViewInflationActivity.java",
     ],
-    platform_apis: true,
+    sdk_version: "26", // Android O (8.0) and higher
 }
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 467d8f7..15785d4 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -71,6 +71,19 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:label="SystemServer Benchmark"
+            android:name=".SystemServerBenchmarkActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
     </application>
 
 </manifest>
diff --git a/startop/apps/test/res/layout/system_server_benchmark_page.xml b/startop/apps/test/res/layout/system_server_benchmark_page.xml
new file mode 100644
index 0000000..337fe65
--- /dev/null
+++ b/startop/apps/test/res/layout/system_server_benchmark_page.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <GridLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:columnCount="3"
+        android:id="@+id/benchmark_list">
+
+    <TextView android:text="Benchmark"/>
+    <TextView android:text="Mean (ms)"/>
+    <TextView android:text="Stdev (ms)"/>
+
+    </GridLayout>
+</ScrollView>
diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java
new file mode 100644
index 0000000..a411e8c
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensive.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+/**
+ *  A threaded CPU intensive class for use in benchmarks.
+ */
+
+package com.android.startop.test;
+
+final class CPUIntensive {
+    public static final int THREAD_COUNT = 8;
+    public static final int ARRAY_SIZE = 30000;
+    public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE];
+
+    static class WorkerThread extends Thread {
+        int mThreadNumber;
+        WorkerThread(int number) {
+            mThreadNumber = number;
+        }
+        public void run() {
+            final int arrayLength = array[mThreadNumber].length;
+            for (int i = 0; i < arrayLength; ++i) {
+                array[mThreadNumber][i] = i * i;
+            }
+            for (int i = 0; i < arrayLength; ++i) {
+                for (int j = 0; j < arrayLength; ++j) {
+                    int swap = array[mThreadNumber][j];
+                    array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength];
+                    array[mThreadNumber][(j + i) % arrayLength] = swap;
+                }
+            }
+        }
+    };
+
+    public static void doSomeWork(int threadCount) {
+        WorkerThread[] threads = new WorkerThread[threadCount];
+        // Create the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i] = new WorkerThread(i);
+        }
+        // Start the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i].start();
+        }
+        // Join the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            try {
+                threads[i].join();
+            } catch (Exception ex) {
+            }
+        }
+    }
+}
+
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..c8d9fde
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -0,0 +1,214 @@
+/*
+ * 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.startop.test;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+
+class Benchmark {
+    // Time limit to run benchmarks in seconds
+    public static final int TIME_LIMIT = 5;
+
+    public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
+        Context context = parent.getContext();
+        Button button = new Button(context);
+        TextView mean = new TextView(context);
+        TextView stdev = new TextView(context);
+
+        button.setText(name);
+        mean.setText("");
+        stdev.setText("");
+
+        button.setOnClickListener((_button) -> {
+            mean.setText("Running...");
+            stdev.setText("");
+
+            new AsyncTask() {
+                double resultMean = 0;
+                double resultStdev = 0;
+
+                @Override
+                protected Object doInBackground(Object... _args) {
+                    long startTime = System.nanoTime();
+                    int count = 0;
+
+                    // Run benchmark
+                    while (true) {
+                        long elapsed = -System.nanoTime();
+                        thunk.run();
+                        elapsed += System.nanoTime();
+
+                        count++;
+                        double elapsedVariance = (double) elapsed - resultMean;
+                        resultMean += elapsedVariance / count;
+                        resultStdev += elapsedVariance * ((double) elapsed - resultMean);
+
+                        if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
+                            break;
+                        }
+                    }
+                    resultStdev = Math.sqrt(resultStdev / (count - 1));
+
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Object _result) {
+                    mean.setText(String.format("%.3f", resultMean / 1e6));
+                    stdev.setText(String.format("%.3f", resultStdev / 1e6));
+                }
+            }.execute(new Object());
+        });
+
+        parent.addView(button);
+        parent.addView(mean);
+        parent.addView(stdev);
+    }
+}
+
+public class SystemServerBenchmarkActivity extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.system_server_benchmark_page);
+
+        GridLayout benchmarkList = findViewById(R.id.benchmark_list);
+
+        new Benchmark(benchmarkList, "Empty", () -> {
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> {
+            CPUIntensive.doSomeWork(1);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> {
+            CPUIntensive.doSomeWork(2);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> {
+            CPUIntensive.doSomeWork(4);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> {
+            CPUIntensive.doSomeWork(8);
+        });
+
+        PackageManager pm = getPackageManager();
+        new Benchmark(benchmarkList, "getInstalledApplications", () -> {
+            pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+        });
+
+        new Benchmark(benchmarkList, "getInstalledPackages", () -> {
+            pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
+        });
+
+        new Benchmark(benchmarkList, "getPackageInfo", () -> {
+            try {
+                pm.getPackageInfo("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "getApplicationInfo", () -> {
+            try {
+                pm.getApplicationInfo("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        try {
+            ApplicationInfo app = pm.getApplicationInfo("com.android.startop.test", 0);
+            new Benchmark(benchmarkList, "getResourcesForApplication", () -> {
+                try {
+                    pm.getResourcesForApplication(app);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+
+            new Benchmark(benchmarkList, "getPackagesForUid", () -> {
+                pm.getPackagesForUid(app.uid);
+            });
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        ComponentName component = new ComponentName(this, this.getClass());
+        new Benchmark(benchmarkList, "getActivityInfo", () -> {
+            try {
+                pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
+            pm.getLaunchIntentForPackage("com.android.startop.test");
+        });
+
+        new Benchmark(benchmarkList, "getPackageUid", () -> {
+            try {
+                pm.getPackageUid("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "checkPermission", () -> {
+            // Check for the first permission I could find.
+            pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
+        });
+
+        new Benchmark(benchmarkList, "checkSignatures", () -> {
+            // Compare with settings, since settings is on both AOSP and Master builds
+            pm.checkSignatures("com.android.settings", "com.android.startop.test");
+        });
+
+        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+        new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
+            pm.queryBroadcastReceivers(intent, 0);
+        });
+
+        new Benchmark(benchmarkList, "hasSystemFeature", () -> {
+            pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        });
+
+        new Benchmark(benchmarkList, "resolveService", () -> {
+            pm.resolveService(intent, 0);
+        });
+
+        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+        new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
+            am.getRunningAppProcesses();
+        });
+
+    }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 31bd341..902da4c 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -275,7 +275,8 @@
             Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
             return false;
         }
-        invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+        invokeRemote(mIorapRemote,
+            (IIorap remote) -> remote.setTaskListener(new RemoteTaskListener()) );
         registerInProcessListenersLocked();
 
         return true;
@@ -323,8 +324,9 @@
                         mSequenceId, intent));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.IntentStarted(mSequenceId, intent))
             );
         }
@@ -335,8 +337,9 @@
                 Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.IntentFailed(mSequenceId))
             );
         }
@@ -349,8 +352,9 @@
                         mSequenceId, activity, temperature));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                             new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature))
             );
         }
@@ -362,8 +366,9 @@
                         mSequenceId, activity));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                             new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId,
                                     activity)));
         }
@@ -375,8 +380,9 @@
                         mSequenceId, activity));
             }
 
-            invokeRemote(() ->
-                mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
             );
         }
@@ -501,8 +507,8 @@
                 mRunningJobs.put(request, params);
             }
 
-            if (!invokeRemote( () ->
-                    mIorapRemote.onJobScheduledEvent(request,
+            if (!invokeRemote(mIorapRemote, (IIorap remote) ->
+                    remote.onJobScheduledEvent(request,
                             JobScheduledEvent.createIdleMaintenance(
                                     JobScheduledEvent.TYPE_START_JOB,
                                     params))
@@ -539,8 +545,8 @@
 
             // Notify iorapd to stop (abort) the job.
             if (wasTracking) {
-                invokeRemote(() ->
-                        mIorapRemote.onJobScheduledEvent(RequestId.nextValueForSequence(),
+                invokeRemote(mIorapRemote, (IIorap remote) ->
+                        remote.onJobScheduledEvent(RequestId.nextValueForSequence(),
                                 JobScheduledEvent.createIdleMaintenance(
                                         JobScheduledEvent.TYPE_STOP_JOB,
                                         params))
@@ -632,12 +638,17 @@
     /** Allow passing lambdas to #invokeRemote */
     private interface RemoteRunnable {
         // TODO: run(RequestId) ?
-        void run() throws RemoteException;
+        void run(IIorap iorap) throws RemoteException;
     }
 
-    private static boolean invokeRemote(RemoteRunnable r) {
+    // Always pass in the iorap directly here to avoid data race.
+    private static boolean invokeRemote(IIorap iorap, RemoteRunnable r) {
+       if (iorap == null) {
+         Log.w(TAG, "IIorap went to null in this thread, drop invokeRemote.");
+         return false;
+       }
        try {
-           r.run();
+           r.run(iorap);
            return true;
        } catch (RemoteException e) {
            // This could be a logic error (remote side returning error), which we need to fix.
diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py
index eb582f9..fa1c4e6 100755
--- a/startop/scripts/app_startup/app_startup_runner.py
+++ b/startop/scripts/app_startup/app_startup_runner.py
@@ -41,11 +41,12 @@
 sys.path.append(os.path.dirname(DIR))
 import lib.cmd_utils as cmd_utils
 import lib.print_utils as print_utils
-import iorap.compiler as compiler
 from app_startup.run_app_with_prefetch import PrefetchAppRunner
 import app_startup.lib.args_utils as args_utils
 from app_startup.lib.data_frame import DataFrame
 from app_startup.lib.perfetto_trace_collector import PerfettoTraceCollector
+from iorap.compiler import CompilerType
+import iorap.compiler as compiler
 
 # The following command line options participate in the combinatorial generation.
 # All other arguments have a global effect.
@@ -58,8 +59,6 @@
 
 CollectorPackageInfo = NamedTuple('CollectorPackageInfo',
                                   [('package', str), ('compiler_filter', str)])
-_COMPILER_SCRIPT = os.path.join(os.path.dirname(os.path.dirname(
-    os.path.realpath(__file__))), 'iorap/compiler.py')
 # by 2; systrace starts up slowly.
 
 _UNLOCK_SCREEN_SCRIPT = os.path.join(
@@ -135,6 +134,10 @@
                               action='append',
                               help='The trace duration (milliseconds) in '
                                    'compilation')
+  optional_named.add_argument('--compiler-type', dest='compiler_type',
+                              type=CompilerType, choices=list(CompilerType),
+                              default=CompilerType.DEVICE,
+                              help='The type of compiler.')
 
   return parser.parse_args(argv)
 
@@ -211,26 +214,26 @@
 
   return DataFrame(d)
 
-def compile_perfetto_trace(inodes_path: str,
+def build_ri_compiler_argv(inodes_path: str,
                            perfetto_trace_file: str,
-                           trace_duration: Optional[timedelta]) -> TextIO:
-  compiler_trace_file = tempfile.NamedTemporaryFile()
-  argv = [_COMPILER_SCRIPT, '-i', inodes_path, '--perfetto-trace',
-          perfetto_trace_file, '-o', compiler_trace_file.name]
+                           trace_duration: Optional[timedelta]
+                           ) -> str:
+  argv = ['-i', inodes_path, '--perfetto-trace',
+          perfetto_trace_file]
 
   if trace_duration is not None:
     argv += ['--duration', str(int(trace_duration.total_seconds()
-                               * PerfettoTraceCollector.MS_PER_SEC))]
+                                   * PerfettoTraceCollector.MS_PER_SEC))]
 
   print_utils.debug_print(argv)
-  compiler.main(argv)
-  return compiler_trace_file
+  return argv
 
 def execute_run_using_perfetto_trace(collector_info,
                                      run_combos: Iterable[RunCommandArgs],
                                      simulate: bool,
                                      inodes_path: str,
-                                     timeout: int) -> DataFrame:
+                                     timeout: int,
+                                     compiler_type: CompilerType) -> DataFrame:
   """ Executes run based on perfetto trace. """
   passed, perfetto_trace_file = run_perfetto_collector(collector_info,
                                                        timeout,
@@ -244,9 +247,15 @@
         if simulate:
           compiler_trace_file = tempfile.NamedTemporaryFile()
         else:
-          compiler_trace_file = compile_perfetto_trace(inodes_path,
-                                                       perfetto_trace_file.name,
-                                                       combos.trace_duration)
+          ri_compiler_argv = build_ri_compiler_argv(inodes_path,
+                                                    perfetto_trace_file.name,
+                                                    combos.trace_duration)
+          compiler_trace_file = compiler.compile(compiler_type,
+                                                 inodes_path,
+                                                 ri_compiler_argv,
+                                                 combos.package,
+                                                 combos.activity)
+
         with compiler_trace_file:
           combos = combos._replace(input=compiler_trace_file.name)
           print_utils.debug_print(combos)
@@ -261,7 +270,8 @@
     grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]],
     simulate: bool,
     inodes_path: str,
-    timeout: int):
+    timeout: int,
+    compiler_type: CompilerType):
   # nothing will work if the screen isn't unlocked first.
   cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT],
                                       timeout,
@@ -273,7 +283,8 @@
                                                 run_combos,
                                                 simulate,
                                                 inodes_path,
-                                                timeout)
+                                                timeout,
+                                                compiler_type)
 
 def gather_results(commands: Iterable[Tuple[DataFrame]],
                    key_list: List[str], value_list: List[Tuple[str, ...]]):
@@ -361,7 +372,8 @@
   exec = execute_run_combos(grouped_combos(),
                             opts.simulate,
                             opts.inodes,
-                            opts.timeout)
+                            opts.timeout,
+                            opts.compiler_type)
 
   results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos())
 
diff --git a/startop/scripts/app_startup/app_startup_runner_test.py b/startop/scripts/app_startup/app_startup_runner_test.py
index 42ea5f0..382f6f3 100755
--- a/startop/scripts/app_startup/app_startup_runner_test.py
+++ b/startop/scripts/app_startup/app_startup_runner_test.py
@@ -92,7 +92,7 @@
   """
   d = {'compiler_filters': None, 'simulate': False, 'debug': False,
        'output': None, 'timeout': 10, 'loop_count': 1, 'inodes': None,
-       'trace_duration': None}
+       'trace_duration': None, 'compiler_type': asr.CompilerType.HOST}
   d.update(kwargs)
   return d
 
diff --git a/startop/scripts/app_startup/lib/adb_utils.py b/startop/scripts/app_startup/lib/adb_utils.py
index e56a968..3cebc9a 100644
--- a/startop/scripts/app_startup/lib/adb_utils.py
+++ b/startop/scripts/app_startup/lib/adb_utils.py
@@ -42,8 +42,8 @@
 def vm_drop_cache():
   """Free pagecache and slab object."""
   cmd_utils.run_adb_shell_command('echo 3 > /proc/sys/vm/drop_caches')
-  # Sleep a little bit to provide enougth time for cache cleanup.
-  time.sleep(2)
+  # Sleep a little bit to provide enough time for cache cleanup.
+  time.sleep(1)
 
 def root():
   """Roots adb and successive adb commands will run under root."""
diff --git a/startop/scripts/iorap/compiler.py b/startop/scripts/iorap/compiler.py
old mode 100755
new mode 100644
index c940fe9..1426d34
--- a/startop/scripts/iorap/compiler.py
+++ b/startop/scripts/iorap/compiler.py
@@ -1,322 +1,73 @@
 #!/usr/bin/env python3
-
 #
-# Copyright (C) 2019 The Android Open Source Project
+# Copyright 2019, The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#      http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
-#
-# Dependencies:
-#
-# $> sudo apt-get install python3-pip
-# $> pip3 install --user protobuf sqlalchemy sqlite3
-#
-
-import optparse
+import importlib
 import os
-import re
 import sys
 import tempfile
-from pathlib import Path
-from datetime import timedelta
-from typing import Iterable, Optional, List
+from enum import Enum
+from typing import TextIO, List
 
+# local import
 DIR = os.path.abspath(os.path.dirname(__file__))
 sys.path.append(os.path.dirname(DIR))
-from iorap.generated.TraceFile_pb2 import *
-from iorap.lib.inode2filename import Inode2Filename
+import lib.print_utils as print_utils
 
-parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-sys.path.append(parent_dir_name)
-from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
-    RawFtraceEntry
-import lib.cmd_utils as cmd_utils
+# Type of compiler.
+class CompilerType(Enum):
+  HOST = 1  # iorap.cmd.compiler on host
+  DEVICE = 2  # adb shell iorap.cmd.compiler
+  RI = 3  # compiler.py
 
-_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
-ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
-TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
-    'external/perfetto/tools/traceconv')
+def compile_perfetto_trace_ri(
+    argv: List[str],
+    compiler) -> TextIO:
+  print_utils.debug_print('Compile using RI compiler.')
+  compiler_trace_file = tempfile.NamedTemporaryFile()
+  argv.extend(['-o', compiler_trace_file.name])
+  print_utils.debug_print(argv)
+  compiler.main([''] + argv)
+  return compiler_trace_file
 
-class PageRun:
-  """
-  Intermediate representation for a run of one or more pages.
-  """
-  def __init__(self, device_number: int, inode: int, offset: int, length: int):
-    self.device_number = device_number
-    self.inode = inode
-    self.offset = offset
-    self.length = length
+def compile_perfetto_trace_device(inodes_path: str,
+                                  package: str,
+                                  activity: str,
+                                  compiler) -> TextIO:
+  print_utils.debug_print('Compile using on-device compiler.')
+  compiler_trace_file = tempfile.NamedTemporaryFile()
+  compiler.main(inodes_path, package, activity, compiler_trace_file.name)
+  return compiler_trace_file
 
-  def __str__(self):
-    return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
-        %(self.device_number, self.inode, self.offset, self.length)
+def compile(compiler_type: CompilerType,
+            inodes_path: str,
+            ri_compiler_argv,
+            package: str,
+            activity: str) -> TextIO:
+  if compiler_type == CompilerType.RI:
+    compiler = importlib.import_module('iorap.compiler_ri')
+    compiler_trace_file = compile_perfetto_trace_ri(ri_compiler_argv,
+                                                    compiler)
+    return compiler_trace_file
+  if compiler_type == CompilerType.DEVICE:
+    compiler = importlib.import_module('iorap.compiler_device')
+    compiler_trace_file = compile_perfetto_trace_device(inodes_path,
+                                                        package,
+                                                        activity,
+                                                        compiler)
+    return compiler_trace_file
 
-def debug_print(msg):
-  #print(msg)
-  pass
-
-UNDER_LAUNCH = False
-
-def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
-  global _PAGE_SIZE
-
-  runs = [
-      PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
-              length=_PAGE_SIZE)
-        for pg_entry in page_cache_entries
-  ]
-
-  for r in runs:
-    debug_print(r)
-
-  print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
-
-  return runs
-
-def optimize_page_runs(page_runs):
-  new_entries = []
-  last_entry = None
-  for pg_entry in page_runs:
-    if last_entry:
-      if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
-        # we are dealing with a run for the same exact file as a previous run.
-        if pg_entry.offset == last_entry.offset + last_entry.length:
-          # trivially contiguous entries. merge them together.
-          last_entry.length += pg_entry.length
-          continue
-    # Default: Add the run without merging it to a previous run.
-    last_entry = pg_entry
-    new_entries.append(pg_entry)
-  return new_entries
-
-def is_filename_matching_filter(file_name, filters=[]):
-  """
-  Blacklist-style regular expression filters.
-
-  :return: True iff file_name has an RE match in one of the filters.
-  """
-  for filt in filters:
-    res = re.search(filt, file_name)
-    if res:
-      return True
-
-  return False
-
-def build_protobuf(page_runs, inode2filename, filters=[]):
-  trace_file = TraceFile()
-  trace_file_index = trace_file.index
-
-  file_id_counter = 0
-  file_id_map = {} # filename -> id
-
-  stats_length_total = 0
-  filename_stats = {} # filename -> total size
-
-  skipped_inode_map = {}
-  filtered_entry_map = {} # filename -> count
-
-  for pg_entry in page_runs:
-    fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
-    if not fn:
-      skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
-      continue
-
-    filename = fn
-
-    if filters and not is_filename_matching_filter(filename, filters):
-      filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
-      continue
-
-    file_id = file_id_map.get(filename)
-    if not file_id:
-      file_id = file_id_counter
-      file_id_map[filename] = file_id_counter
-      file_id_counter = file_id_counter + 1
-
-      file_index_entry = trace_file_index.entries.add()
-      file_index_entry.id = file_id
-      file_index_entry.file_name = filename
-
-    # already in the file index, add the file entry.
-    file_entry = trace_file.list.entries.add()
-    file_entry.index_id = file_id
-    file_entry.file_length = pg_entry.length
-    stats_length_total += file_entry.file_length
-    file_entry.file_offset = pg_entry.offset
-
-    filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
-
-  for inode, count in skipped_inode_map.items():
-    print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
-
-  print("Stats: Sum of lengths %d" %(stats_length_total))
-
-  if filters:
-    print("Filter: %d total files removed." %(len(filtered_entry_map)))
-
-    for fn, count in filtered_entry_map.items():
-      print("Filter: File '%s' removed '%d' entries." %(fn, count))
-
-  for filename, file_size in filename_stats.items():
-    print("%s,%s" %(filename, file_size))
-
-  return trace_file
-
-def calc_trace_end_time(trace2db: Trace2Db,
-                        trace_duration: Optional[timedelta]) -> float:
-  """
-  Calculates the end time based on the trace duration.
-  The start time is the first receiving mm file map event.
-  The end time is the start time plus the trace duration.
-  All of them are in milliseconds.
-  """
-  # If the duration is not set, assume all time is acceptable.
-  if trace_duration is None:
-    # float('inf')
-    return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
-
-  first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
-      MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
-      RawFtraceEntry.timestamp).first()
-
-  return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
-
-def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
-  end_time = calc_trace_end_time(trace2db, trace_duration)
-  # SELECT * FROM tbl ORDER BY id;
-  return trace2db.session.query(MmFilemapAddToPageCache).join(
-      MmFilemapAddToPageCache.raw_ftrace_entry).filter(
-      RawFtraceEntry.timestamp <= end_time).order_by(
-      MmFilemapAddToPageCache.id).all()
-
-def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
-                                         path_to_tmp_systrace: str) -> None:
-  """ Transforms the systrace file from perfetto trace. """
-  cmd_utils.run_command_nofail([str(TRACECONV_BIN),
-                                'systrace',
-                                path_to_perfetto_trace,
-                                path_to_tmp_systrace])
-
-
-def run(sql_db_path:str,
-        trace_file:str,
-        trace_duration:Optional[timedelta],
-        output_file:str,
-        inode_table:str,
-        filter:List[str]) -> int:
-  trace2db = Trace2Db(sql_db_path)
-  # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
-  trace2db.set_raw_ftrace_entry_filter(\
-      lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
-  # TODO: parse multiple trace files here.
-  parse_count = trace2db.parse_file_into_db(trace_file)
-
-  mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
-                                                              trace_duration)
-  print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
-
-  page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
-  print("DONE. Converted %d entries" %(len(page_runs)))
-
-  # TODO: flags to select optimizations.
-  optimized_page_runs = optimize_page_runs(page_runs)
-  print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
-
-  print("Build protobuf...")
-  trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
-
-  print("Write protobuf to file...")
-  output_file = open(output_file, 'wb')
-  output_file.write(trace_file.SerializeToString())
-  output_file.close()
-
-  print("DONE")
-
-  # TODO: Silent running mode [no output except on error] for build runs.
-
-  return 0
-
-def main(argv):
-  parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
-  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
-                    help='Read cached inode data from a file saved earlier with pagecache.py -d')
-  parser.add_option('-t', dest='trace_file', metavar='FILE',
-                    help='Path to systrace file (trace.html) that will be parsed')
-  parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
-                    metavar='FILE',
-                    help='Path to perfetto trace that will be parsed')
-
-  parser.add_option('--db', dest='sql_db', metavar='FILE',
-                    help='Path to intermediate sqlite3 database [default: in-memory].')
-
-  parser.add_option('-f', dest='filter', action="append", default=[],
-                    help="Add file filter. All file entries not matching one of the filters are discarded.")
-
-  parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
-                    help="Exclude all events not inside launch_lock")
-
-  parser.add_option('-o', dest='output_file', metavar='FILE',
-                    help='Output protobuf file')
-
-  parser.add_option('--duration', dest='trace_duration', action="store",
-                    type=int, help='The duration of trace in milliseconds.')
-
-  options, categories = parser.parse_args(argv[1:])
-
-  # TODO: OptionParser should have some flags to make these mandatory.
-  if not options.inode_data_file:
-    parser.error("-i is required")
-  if not options.trace_file and not options.perfetto_trace_file:
-    parser.error("one of -t or --perfetto-trace is required")
-  if options.trace_file and options.perfetto_trace_file:
-    parser.error("please enter either -t or --perfetto-trace, not both")
-  if not options.output_file:
-    parser.error("-o is required")
-
-  if options.launch_lock:
-    print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
-
-  inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
-
-  sql_db_path = ":memory:"
-  if options.sql_db:
-    sql_db_path = options.sql_db
-
-  trace_duration = timedelta(milliseconds=options.trace_duration) if \
-    options.trace_duration is not None else None
-
-  # if the input is systrace
-  if options.trace_file:
-    return run(sql_db_path,
-               options.trace_file,
-               trace_duration,
-               options.output_file,
-               inode_table,
-               options.filter)
-
-  # if the input is perfetto trace
-  # TODO python 3.7 switch to using nullcontext
-  with tempfile.NamedTemporaryFile() as trace_file:
-    transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
-                                         trace_file.name)
-    return run(sql_db_path,
-               trace_file.name,
-               trace_duration,
-               options.output_file,
-               inode_table,
-               options.filter)
-
-if __name__ == '__main__':
-  print(sys.argv)
-  sys.exit(main(sys.argv))
+  # Should not arrive here.
+  raise ValueError('Unknown compiler type')
diff --git a/startop/scripts/iorap/compiler_device.py b/startop/scripts/iorap/compiler_device.py
new file mode 100644
index 0000000..d941cd9
--- /dev/null
+++ b/startop/scripts/iorap/compiler_device.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import sys
+from typing import List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))  # framework/base/startop/script
+import lib.print_utils as print_utils
+import iorap.lib.iorapd_utils as iorapd_utils
+from app_startup.lib.app_runner import AppRunner
+
+IORAP_COMMON_BASH_SCRIPT = os.path.join(DIR, 'common')
+
+def parse_options(argv: List[str] = None):
+  """Parses command line arguments and returns an argparse Namespace object."""
+  parser = argparse.ArgumentParser(description="Compile perfetto trace file")
+  required_named = parser.add_argument_group('required named arguments')
+
+  required_named.add_argument('-i', dest='inodes', metavar='FILE',
+                              help='Read cached inode data from a file saved '
+                                   'earlier with pagecache.py -d')
+  required_named.add_argument('-p', dest='package',
+                              help='Package of the app to be compiled')
+
+  optional_named = parser.add_argument_group('optional named arguments')
+  optional_named.add_argument('-o', dest='output',
+                              help='The compiled trace is stored into the output file')
+  optional_named.add_argument('-a', dest='activity',
+                              help='Activity of the app to be compiled')
+  optional_named.add_argument('-d', dest='debug', action='store_true'
+                              , help='Activity of the app to be compiled')
+
+  return parser.parse_args(argv)
+
+def main(inodes, package, activity, output, **kwargs) -> int:
+  """Entries of the program."""
+  if not activity:
+    activity = AppRunner.get_activity(package)
+
+  passed = iorapd_utils.compile_perfetto_trace_on_device(package, activity,
+                                                         inodes)
+  if passed and output:
+    iorapd_utils.get_iorapd_compiler_trace(package, activity, output)
+
+  return 0
+
+if __name__ == '__main__':
+  opts = parse_options()
+  if opts.debug:
+    print_utils.DEBUG = opts.debug
+  print_utils.debug_print(opts)
+  sys.exit(main(**(vars(opts))))
diff --git a/startop/scripts/iorap/compiler_ri.py b/startop/scripts/iorap/compiler_ri.py
new file mode 100755
index 0000000..90fc8a8
--- /dev/null
+++ b/startop/scripts/iorap/compiler_ri.py
@@ -0,0 +1,325 @@
+#!/usr/bin/env python3
+
+#
+# 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.
+#
+
+#
+# Dependencies:
+#
+# $> sudo apt-get install python3-pip
+# $> pip3 install --user protobuf sqlalchemy sqlite3
+#
+
+import optparse
+import os
+import re
+import sys
+import tempfile
+from pathlib import Path
+from datetime import timedelta
+from typing import Iterable, Optional, List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))
+from iorap.generated.TraceFile_pb2 import *
+from iorap.lib.inode2filename import Inode2Filename
+
+parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.append(parent_dir_name)
+from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
+    RawFtraceEntry
+import lib.cmd_utils as cmd_utils
+
+_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
+ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
+TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
+    'external/perfetto/tools/traceconv')
+
+class PageRun:
+  """
+  Intermediate representation for a run of one or more pages.
+  """
+  def __init__(self, device_number: int, inode: int, offset: int, length: int):
+    self.device_number = device_number
+    self.inode = inode
+    self.offset = offset
+    self.length = length
+
+  def __str__(self):
+    return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
+        %(self.device_number, self.inode, self.offset, self.length)
+
+def debug_print(msg):
+  #print(msg)
+  pass
+
+UNDER_LAUNCH = False
+
+def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
+  global _PAGE_SIZE
+
+  runs = [
+      PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
+              length=_PAGE_SIZE)
+        for pg_entry in page_cache_entries
+  ]
+
+  for r in runs:
+    debug_print(r)
+
+  print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
+
+  return runs
+
+def optimize_page_runs(page_runs):
+  new_entries = []
+  last_entry = None
+  for pg_entry in page_runs:
+    if last_entry:
+      if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
+        # we are dealing with a run for the same exact file as a previous run.
+        if pg_entry.offset == last_entry.offset + last_entry.length:
+          # trivially contiguous entries. merge them together.
+          last_entry.length += pg_entry.length
+          continue
+    # Default: Add the run without merging it to a previous run.
+    last_entry = pg_entry
+    new_entries.append(pg_entry)
+  return new_entries
+
+def is_filename_matching_filter(file_name, filters=[]):
+  """
+  Blacklist-style regular expression filters.
+
+  :return: True iff file_name has an RE match in one of the filters.
+  """
+  for filt in filters:
+    res = re.search(filt, file_name)
+    if res:
+      return True
+
+  return False
+
+def build_protobuf(page_runs, inode2filename, filters=[]):
+  trace_file = TraceFile()
+  trace_file_index = trace_file.index
+
+  file_id_counter = 0
+  file_id_map = {} # filename -> id
+
+  stats_length_total = 0
+  filename_stats = {} # filename -> total size
+
+  skipped_inode_map = {}
+  filtered_entry_map = {} # filename -> count
+
+  for pg_entry in page_runs:
+    fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
+    if not fn:
+      skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
+      continue
+
+    filename = fn
+
+    if filters and not is_filename_matching_filter(filename, filters):
+      filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
+      continue
+
+    file_id = file_id_map.get(filename)
+    # file_id could 0, which satisfies "if file_id" and causes duplicate
+    # filename for file id 0.
+    if file_id is None:
+      file_id = file_id_counter
+      file_id_map[filename] = file_id_counter
+      file_id_counter = file_id_counter + 1
+
+      file_index_entry = trace_file_index.entries.add()
+      file_index_entry.id = file_id
+      file_index_entry.file_name = filename
+
+    # already in the file index, add the file entry.
+    file_entry = trace_file.list.entries.add()
+    file_entry.index_id = file_id
+    file_entry.file_length = pg_entry.length
+    stats_length_total += file_entry.file_length
+    file_entry.file_offset = pg_entry.offset
+
+    filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
+
+  for inode, count in skipped_inode_map.items():
+    print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
+
+  print("Stats: Sum of lengths %d" %(stats_length_total))
+
+  if filters:
+    print("Filter: %d total files removed." %(len(filtered_entry_map)))
+
+    for fn, count in filtered_entry_map.items():
+      print("Filter: File '%s' removed '%d' entries." %(fn, count))
+
+  for filename, file_size in filename_stats.items():
+    print("%s,%s" %(filename, file_size))
+
+  return trace_file
+
+def calc_trace_end_time(trace2db: Trace2Db,
+                        trace_duration: Optional[timedelta]) -> float:
+  """
+  Calculates the end time based on the trace duration.
+  The start time is the first receiving mm file map event.
+  The end time is the start time plus the trace duration.
+  All of them are in milliseconds.
+  """
+  # If the duration is not set, assume all time is acceptable.
+  if trace_duration is None:
+    # float('inf')
+    return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
+
+  first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
+      MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
+      RawFtraceEntry.timestamp).first()
+
+  # total_seconds() will return a float number.
+  return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
+
+def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
+  end_time = calc_trace_end_time(trace2db, trace_duration)
+  # SELECT * FROM tbl ORDER BY id;
+  return trace2db.session.query(MmFilemapAddToPageCache).join(
+      MmFilemapAddToPageCache.raw_ftrace_entry).filter(
+      RawFtraceEntry.timestamp <= end_time).order_by(
+      MmFilemapAddToPageCache.id).all()
+
+def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
+                                         path_to_tmp_systrace: str) -> None:
+  """ Transforms the systrace file from perfetto trace. """
+  cmd_utils.run_command_nofail([str(TRACECONV_BIN),
+                                'systrace',
+                                path_to_perfetto_trace,
+                                path_to_tmp_systrace])
+
+
+def run(sql_db_path:str,
+        trace_file:str,
+        trace_duration:Optional[timedelta],
+        output_file:str,
+        inode_table:str,
+        filter:List[str]) -> int:
+  trace2db = Trace2Db(sql_db_path)
+  # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
+  trace2db.set_raw_ftrace_entry_filter(\
+      lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
+  # TODO: parse multiple trace files here.
+  parse_count = trace2db.parse_file_into_db(trace_file)
+
+  mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
+                                                              trace_duration)
+  print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
+
+  page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
+  print("DONE. Converted %d entries" %(len(page_runs)))
+
+  # TODO: flags to select optimizations.
+  optimized_page_runs = optimize_page_runs(page_runs)
+  print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
+
+  print("Build protobuf...")
+  trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
+
+  print("Write protobuf to file...")
+  output_file = open(output_file, 'wb')
+  output_file.write(trace_file.SerializeToString())
+  output_file.close()
+
+  print("DONE")
+
+  # TODO: Silent running mode [no output except on error] for build runs.
+
+  return 0
+
+def main(argv):
+  parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
+  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+                    help='Read cached inode data from a file saved earlier with pagecache.py -d')
+  parser.add_option('-t', dest='trace_file', metavar='FILE',
+                    help='Path to systrace file (trace.html) that will be parsed')
+  parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
+                    metavar='FILE',
+                    help='Path to perfetto trace that will be parsed')
+
+  parser.add_option('--db', dest='sql_db', metavar='FILE',
+                    help='Path to intermediate sqlite3 database [default: in-memory].')
+
+  parser.add_option('-f', dest='filter', action="append", default=[],
+                    help="Add file filter. All file entries not matching one of the filters are discarded.")
+
+  parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
+                    help="Exclude all events not inside launch_lock")
+
+  parser.add_option('-o', dest='output_file', metavar='FILE',
+                    help='Output protobuf file')
+
+  parser.add_option('--duration', dest='trace_duration', action="store",
+                    type=int, help='The duration of trace in milliseconds.')
+
+  options, categories = parser.parse_args(argv[1:])
+
+  # TODO: OptionParser should have some flags to make these mandatory.
+  if not options.inode_data_file:
+    parser.error("-i is required")
+  if not options.trace_file and not options.perfetto_trace_file:
+    parser.error("one of -t or --perfetto-trace is required")
+  if options.trace_file and options.perfetto_trace_file:
+    parser.error("please enter either -t or --perfetto-trace, not both")
+  if not options.output_file:
+    parser.error("-o is required")
+
+  if options.launch_lock:
+    print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
+
+  inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
+
+  sql_db_path = ":memory:"
+  if options.sql_db:
+    sql_db_path = options.sql_db
+
+  trace_duration = timedelta(milliseconds=options.trace_duration) if \
+    options.trace_duration is not None else None
+
+  # if the input is systrace
+  if options.trace_file:
+    return run(sql_db_path,
+               options.trace_file,
+               trace_duration,
+               options.output_file,
+               inode_table,
+               options.filter)
+
+  # if the input is perfetto trace
+  # TODO python 3.7 switch to using nullcontext
+  with tempfile.NamedTemporaryFile() as trace_file:
+    transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
+                                         trace_file.name)
+    return run(sql_db_path,
+               trace_file.name,
+               trace_duration,
+               options.output_file,
+               inode_table,
+               options.filter)
+
+if __name__ == '__main__':
+  print(sys.argv)
+  sys.exit(main(sys.argv))
diff --git a/startop/scripts/iorap/compiler_test.py b/startop/scripts/iorap/compiler_test.py
index 1a9f059..d1f11c5 100644
--- a/startop/scripts/iorap/compiler_test.py
+++ b/startop/scripts/iorap/compiler_test.py
@@ -30,7 +30,7 @@
 """
 import os
 
-import compiler
+import compiler_host as compiler
 
 DIR = os.path.abspath(os.path.dirname(__file__))
 TEXTCACHE = os.path.join(DIR, 'test_fixtures/compiler/common_textcache')
diff --git a/startop/scripts/iorap/lib/iorapd_utils.py b/startop/scripts/iorap/lib/iorapd_utils.py
index 0d62180..f6f21fd 100644
--- a/startop/scripts/iorap/lib/iorapd_utils.py
+++ b/startop/scripts/iorap/lib/iorapd_utils.py
@@ -18,10 +18,9 @@
 
 import os
 import sys
-from pathlib import Path
 
-# up to two level, like '../../'
-sys.path.append(Path(os.path.abspath(__file__)).parents[2])
+# up to two level
+sys.path.append(os.path.join(os.path.abspath(__file__),'../..'))
 import lib.cmd_utils as cmd_utils
 
 IORAPID_LIB_DIR = os.path.abspath(os.path.dirname(__file__))
@@ -39,6 +38,22 @@
   # Match logic of 'AppComponentName' in iorap::compiler C++ code.
   return '{}/{}%2F{}.{}'.format(IORAPD_DATA_PATH, package, activity, suffix)
 
+def compile_perfetto_trace_on_device(package: str, activity: str,
+                                     inodes: str) -> bool:
+  """Compiles the perfetto trace using on-device compiler."""
+  passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT,
+                                       'iorapd_compiler_for_app_trace',
+                                       [package, activity, inodes])
+  return passed
+
+def get_iorapd_compiler_trace(package: str, activity: str, dest: str) -> str:
+  """Gets compiler trace to dest file."""
+  src = _iorapd_path_to_data_file(package, activity, 'compiled_trace.pb')
+  passed, _ = cmd_utils.run_shell_command('adb pull "{}" "{}"'.format(src, dest))
+  if not passed:
+    return False
+  return True
+
 def iorapd_compiler_install_trace_file(package: str, activity: str,
                                        input_file: str) -> bool:
   """Installs a compiled trace file.
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 4f6524e..c380d29 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -25,6 +25,7 @@
         "slicer",
     ],
     static_libs: [
+        "libcutils",
         "libtinyxml2",
         "liblog",
         "libutils",
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 1214538..f783aa6 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -37,15 +37,21 @@
 android_test {
     name: "dex-builder-test",
     srcs: [
+        "src/android/startop/test/ApkLayoutCompilerTest.java",
         "src/android/startop/test/DexBuilderTest.java",
         "src/android/startop/test/LayoutCompilerTest.java",
         "src/android/startop/test/TestClass.java",
     ],
     sdk_version: "current",
-    data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"],
+    data: [
+        ":generate_dex_testcases",
+        ":generate_compiled_layout1",
+        ":generate_compiled_layout2",
+    ],
     static_libs: [
-        "androidx.test.rules",
-        "guava",
+        "androidx.test.core",
+        "androidx.test.runner",
+        "junit",
     ],
     manifest: "AndroidManifest.xml",
     resource_dirs: ["res"],
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
new file mode 100644
index 0000000..230e8df
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.startop.test;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import android.view.View;
+import dalvik.system.PathClassLoader;
+import java.lang.reflect.Method;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ApkLayoutCompilerTest {
+    static ClassLoader loadDexFile() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
+                ClassLoader.getSystemClassLoader());
+    }
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        // ensure PackageManager has compiled the layouts.
+        Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
+        pm.waitFor();
+    }
+
+    @Test
+    public void loadAndInflateLayout1() throws Exception {
+        ClassLoader dex_file = loadDexFile();
+        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+        Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
+        Context context = InstrumentationRegistry.getTargetContext();
+        layout1.invoke(null, context, R.layout.layout1);
+    }
+
+    @Test
+    public void loadAndInflateLayout2() throws Exception {
+        ClassLoader dex_file = loadDexFile();
+        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+        Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
+        Context context = InstrumentationRegistry.getTargetContext();
+        layout2.invoke(null, context, R.layout.layout2);
+    }
+}
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index d1fe588..6af01f6f 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -14,8 +14,11 @@
 
 package android.startop.test;
 
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
 import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
index 3dfb20c..b0cf91d 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
@@ -15,11 +15,11 @@
 package android.startop.test;
 
 import android.content.Context;
-
 import androidx.test.InstrumentationRegistry;
-
+import android.view.View;
 import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.Method;
+import org.junit.Assert;
 import org.junit.Test;
 
 import java.lang.reflect.Method;
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 6b9b83d..8b8c86b 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -16,6 +16,8 @@
 
 package android.telecom;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -81,7 +83,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj == null) {
             return false;
         }
@@ -93,6 +95,7 @@
                 getSupportedRouteMask() == state.getSupportedRouteMask();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return String.format(Locale.US,
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 0e0406d..e4f8d11 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,7 +16,6 @@
 
 package android.telecom;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
@@ -33,9 +32,6 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -75,7 +71,7 @@
  *
  * public void requestRole() {
  *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
- *     Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING");
+ *     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING);
  *     startActivityForResult(intent, REQUEST_ID);
  * }
  *
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3548810..0abd9fc 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.telecom.Logging.Session;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IConnectionService;
 import com.android.internal.telecom.IConnectionServiceAdapter;
@@ -2672,4 +2673,13 @@
             return ++mId;
         }
     }
+
+    /**
+     * Returns this handler, ONLY FOR TESTING.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
 }
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 949f7b7..49c3a72 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -391,6 +391,20 @@
         return mCurrentThreadId.get();
     }
 
+    /**
+     * @return A String representation of the active sessions at the time that this method is
+     * called.
+     */
+    @VisibleForTesting
+    public synchronized String printActiveSessions() {
+        StringBuilder message = new StringBuilder();
+        for (ConcurrentHashMap.Entry<Integer, Session> entry : mSessionMapper.entrySet()) {
+            message.append(entry.getValue().printFullSessionTree());
+            message.append("\n");
+        }
+        return message.toString();
+    }
+
     @VisibleForTesting
     public synchronized void cleanupStaleSessions(long timeoutMs) {
         String logMessage = "Stale Sessions Cleaned:\n";
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 61f425e..67b252e 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3943,9 +3943,10 @@
     }
 
     /**
-     * Contains received SMS cell broadcast messages.
+     * Contains received SMS cell broadcast messages. More details are available in 3GPP TS 23.041.
      * @hide
      */
+    @SystemApi
     public static final class CellBroadcasts implements BaseColumns {
 
         /**
@@ -3957,30 +3958,59 @@
         /**
          * The {@code content://} URI for this table.
          */
+        @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
 
         /**
-         * Message geographical scope.
+         * The id of the subscription which received this cell broadcast message.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String SUB_ID = "sub_id";
+
+        /**
+         * Message geographical scope. Valid values are:
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE}. meaning the
+         * message is for the radio service cell</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
+         * meaning the message is for the radio service cell and immediately displayed</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_PLMN_WIDE}, meaning the
+         * message is for the PLMN (i.e. MCC/MNC)</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE},
+         * meaning the message is for the location area (in GSM) or service area (in UMTS)</li>
+         * </ul>
+         *
+         * <p>A message meant for a particular scope is automatically dismissed when the device
+         * exits that scope.</p>
          * <P>Type: INTEGER</P>
          */
         public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
 
         /**
          * Message serial number.
+         * <p>
+         * A 16-bit integer which identifies a particular CBS (cell
+         * broadcast short message service) message. The core network is responsible for
+         * allocating this value, and the value may be managed cyclically (3GPP TS 23.041 section
+         * 9.2.1) once the serial message has been incremented a sufficient number of times.
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERIAL_NUMBER = "serial_number";
 
         /**
-         * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
-         * a broadcast for duplicate detection purposes.
+         * PLMN (i.e. MCC/MNC) of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID}
+         * uniquely identifies a broadcast for duplicate detection purposes.
          * <P>Type: TEXT</P>
          */
         public static final String PLMN = "plmn";
 
         /**
-         * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
-         * Only included if Geographical Scope of message is not PLMN wide (01).
+         * Location area code (LAC).
+         * <p>Code representing location area (GSM) or service area (UMTS) of broadcast sender.
+         * Unused for CDMA. Only included if Geographical Scope of message is not PLMN wide (01).
+         * This value is sent by the network based on the cell tower.
          * <P>Type: INTEGER</P>
          */
         public static final String LAC = "lac";
@@ -3995,23 +4025,29 @@
         /**
          * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_CODE = "message_code";
 
         /**
          * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_IDENTIFIER = "message_id";
 
         /**
-         * Service category (GSM/UMTS: message identifier; CDMA: service category).
+         * Service category which represents the general topic of the message.
+         * <p>
+         * For GSM/UMTS: message identifier (see 3GPP TS 23.041 section 9.4.1.2.2)
+         * For CDMA: a 16-bit CDMA service category (see 3GPP2 C.R1001-D section 9.3)
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERVICE_CATEGORY = "service_category";
 
         /**
-         * Message language code.
+         * Message language code. (See 3GPP TS 23.041 section 9.4.1.2.3 for details).
          * <P>Type: TEXT</P>
          */
         public static final String LANGUAGE_CODE = "language";
@@ -4024,6 +4060,7 @@
 
         /**
          * Message delivery time.
+         * <p>This value is a system timestamp using {@link System#currentTimeMillis}</p>
          * <P>Type: INTEGER (long)</P>
          */
         public static final String DELIVERY_TIME = "date";
@@ -4035,25 +4072,36 @@
         public static final String MESSAGE_READ = "read";
 
         /**
-         * Message format (3GPP or 3GPP2).
+         * Message format ({@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP} or
+         * {@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP2}).
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_FORMAT = "format";
 
         /**
-         * Message priority (including emergency).
+         * Message priority.
+         * <p>This includes
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_NORMAL}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_INTERACTIVE}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_URGENT}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_EMERGENCY}</li>
+         * </p>
+         * </ul>
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_PRIORITY = "priority";
 
         /**
-         * ETWS warning type (ETWS alerts only).
+         * ETWS (Earthquake and Tsunami Warning System) warning type (ETWS alerts only).
+         * <p>See {@link android.telephony.SmsCbEtwsInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String ETWS_WARNING_TYPE = "etws_warning_type";
 
         /**
-         * CMAS message class (CMAS alerts only).
+         * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only).
+         * <p>See {@link android.telephony.SmsCbCmasInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
@@ -4094,12 +4142,14 @@
         /**
          * The timestamp in millisecond of when the device received the message.
          * <P>Type: BIGINT</P>
+         * @hide
          */
         public static final String RECEIVED_TIME = "received_time";
 
         /**
          * Indicates that whether the message has been broadcasted to the application.
          * <P>Type: BOOLEAN</P>
+         * @hide
          */
         public static final String MESSAGE_BROADCASTED = "message_broadcasted";
 
@@ -4135,12 +4185,27 @@
          * "circle|0,0|100;polygon|0,0|0,1.5|1,1|1,0;circle|100.123,100|200.123"
          *
          * <P>Type: TEXT</P>
+         * @hide
          */
         public static final String GEOMETRIES = "geometries";
 
         /**
-         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * Geo-Fencing Maximum Wait Time in second. The range of the time is [0, 255]. A device
+         * shall allow to determine its position meeting operator policy. If the device is unable to
+         * determine its position meeting operator policy within the GeoFencing Maximum Wait Time,
+         * it shall present the alert to the user and discontinue further positioning determination
+         * for the alert.
+         *
+         * <P>Type: INTEGER</P>
+         * @hide
          */
+        public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
+
+        /**
+         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * @hide
+         */
+        @NonNull
         public static final String[] QUERY_COLUMNS = {
                 _ID,
                 GEOGRAPHICAL_SCOPE,
@@ -4166,6 +4231,7 @@
 
         /**
          * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects.
+         * @hide
          */
         public static final String[] QUERY_COLUMNS_FWK = {
                 _ID,
@@ -4188,7 +4254,8 @@
                 CMAS_CERTAINTY,
                 RECEIVED_TIME,
                 MESSAGE_BROADCASTED,
-                GEOMETRIES
+                GEOMETRIES,
+                MAXIMUM_WAIT_TIME
         };
     }
 
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 3a34005..1c03d80 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -44,6 +45,7 @@
         this.mCallQuality = callQuality;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
@@ -109,7 +111,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof CallAttributes) || hashCode() != o.hashCode()) {
             return false;
         }
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index 10a04a9..028280c 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -252,6 +254,7 @@
     }
 
     // Parcelable things
+    @NonNull
     @Override
     public String toString() {
         return "CallQuality: {downlinkCallQualityLevel=" + mDownlinkCallQualityLevel
@@ -285,7 +288,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) {
             return false;
         }
diff --git a/telephony/java/android/telephony/CallerInfo.java b/telephony/java/android/telephony/CallerInfo.java
new file mode 100644
index 0000000..f87ac50
--- /dev/null
+++ b/telephony/java/android/telephony/CallerInfo.java
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.location.Country;
+import android.location.CountryDetector;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
+
+import com.android.internal.annotations.VisibleForTesting;
+import java.util.Locale;
+
+
+/**
+ * Looks up caller information for the given phone number.
+ *
+ * {@hide}
+ */
+@SystemApi
+public class CallerInfo {
+    private static final String TAG = "CallerInfo";
+    private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
+
+    /** @hide */
+    public static final long USER_TYPE_CURRENT = 0;
+    /** @hide */
+    public static final long USER_TYPE_WORK = 1;
+
+    /**
+     * Please note that, any one of these member variables can be null,
+     * and any accesses to them should be prepared to handle such a case.
+     *
+     * Also, it is implied that phoneNumber is more often populated than
+     * name is, (think of calls being dialed/received using numbers where
+     * names are not known to the device), so phoneNumber should serve as
+     * a dependable fallback when name is unavailable.
+     *
+     * One other detail here is that this CallerInfo object reflects
+     * information found on a connection, it is an OUTPUT that serves
+     * mainly to display information to the user.  In no way is this object
+     * used as input to make a connection, so we can choose to display
+     * whatever human-readable text makes sense to the user for a
+     * connection.  This is especially relevant for the phone number field,
+     * since it is the one field that is most likely exposed to the user.
+     *
+     * As an example:
+     *   1. User dials "911"
+     *   2. Device recognizes that this is an emergency number
+     *   3. We use the "Emergency Number" string instead of "911" in the
+     *     phoneNumber field.
+     *
+     * What we're really doing here is treating phoneNumber as an essential
+     * field here, NOT name.  We're NOT always guaranteed to have a name
+     * for a connection, but the number should be displayable.
+     */
+    private String name;
+    private String phoneNumber;
+    /** @hide */
+    public String normalizedNumber;
+    /** @hide */
+    public String geoDescription;
+    /** @hide */
+    public String cnapName;
+    /** @hide */
+    public int numberPresentation;
+    /** @hide */
+    public int namePresentation;
+    /** @hide */
+    public boolean contactExists;
+    /** @hide */
+    public String phoneLabel;
+    /**
+     * Split up the phoneLabel into number type and label name.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int    numberType;
+    /** @hide */
+    @UnsupportedAppUsage
+    public String numberLabel;
+    /** @hide */
+    public int photoResource;
+
+    // Contact ID, which will be 0 if a contact comes from the corp CP2.
+    private long contactIdOrZero;
+    /** @hide */
+    public boolean needUpdate;
+    /** @hide */
+    public Uri contactRefUri;
+    /** @hide */
+    public String lookupKey;
+    /** @hide */
+    public ComponentName preferredPhoneAccountComponent;
+    /** @hide */
+    public String preferredPhoneAccountId;
+    /** @hide */
+    public long userType;
+
+    /**
+     * Contact display photo URI.  If a contact has no display photo but a thumbnail, it'll be
+     * the thumbnail URI instead.
+     */
+    private Uri contactDisplayPhotoUri;
+
+    // fields to hold individual contact preference data,
+    // including the send to voicemail flag and the ringtone
+    // uri reference.
+    /** @hide */
+    public Uri contactRingtoneUri;
+    /** @hide */
+    public boolean shouldSendToVoicemail;
+
+    /**
+     * Drawable representing the caller image.  This is essentially
+     * a cache for the image data tied into the connection /
+     * callerinfo object.
+     *
+     * This might be a high resolution picture which is more suitable
+     * for full-screen image view than for smaller icons used in some
+     * kinds of notifications.
+     *
+     * The {@link #isCachedPhotoCurrent} flag indicates if the image
+     * data needs to be reloaded.
+     *
+     * @hide
+     */
+    public Drawable cachedPhoto;
+    /**
+     * Bitmap representing the caller image which has possibly lower
+     * resolution than {@link #cachedPhoto} and thus more suitable for
+     * icons (like notification icons).
+     *
+     * In usual cases this is just down-scaled image of {@link #cachedPhoto}.
+     * If the down-scaling fails, this will just become null.
+     *
+     * The {@link #isCachedPhotoCurrent} flag indicates if the image
+     * data needs to be reloaded.
+     *
+     * @hide
+     */
+    public Bitmap cachedPhotoIcon;
+    /**
+     * Boolean which indicates if {@link #cachedPhoto} and
+     * {@link #cachedPhotoIcon} is fresh enough. If it is false,
+     * those images aren't pointing to valid objects.
+     *
+     * @hide
+     */
+    public boolean isCachedPhotoCurrent;
+
+    private boolean mIsEmergency;
+    private boolean mIsVoiceMail;
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public CallerInfo() {
+        // TODO: Move all the basic initialization here?
+        mIsEmergency = false;
+        mIsVoiceMail = false;
+        userType = USER_TYPE_CURRENT;
+    }
+
+    /**
+     * getCallerInfo given a Cursor.
+     * @param context the context used to retrieve string constants
+     * @param contactRef the URI to attach to this CallerInfo object
+     * @param cursor the first object in the cursor is used to build the CallerInfo object.
+     * @return the CallerInfo which contains the caller id for the given
+     * number. The returned CallerInfo is null if no number is supplied.
+     *
+     * @hide
+     */
+    public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
+        CallerInfo info = new CallerInfo();
+        info.photoResource = 0;
+        info.phoneLabel = null;
+        info.numberType = 0;
+        info.numberLabel = null;
+        info.cachedPhoto = null;
+        info.isCachedPhotoCurrent = false;
+        info.contactExists = false;
+        info.userType = USER_TYPE_CURRENT;
+
+        if (VDBG) Rlog.v(TAG, "getCallerInfo() based on cursor...");
+
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                // TODO: photo_id is always available but not taken
+                // care of here. Maybe we should store it in the
+                // CallerInfo object as well.
+
+                int columnIndex;
+
+                // Look for the name
+                columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
+                if (columnIndex != -1) {
+                    info.name = cursor.getString(columnIndex);
+                }
+
+                // Look for the number
+                columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
+                if (columnIndex != -1) {
+                    info.phoneNumber = cursor.getString(columnIndex);
+                }
+
+                // Look for the normalized number
+                columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
+                if (columnIndex != -1) {
+                    info.normalizedNumber = cursor.getString(columnIndex);
+                }
+
+                // Look for the label/type combo
+                columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
+                if (columnIndex != -1) {
+                    int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
+                    if (typeColumnIndex != -1) {
+                        info.numberType = cursor.getInt(typeColumnIndex);
+                        info.numberLabel = cursor.getString(columnIndex);
+                        info.phoneLabel = Phone.getDisplayLabel(context,
+                                info.numberType, info.numberLabel)
+                                .toString();
+                    }
+                }
+
+                // Look for the person_id.
+                columnIndex = getColumnIndexForPersonId(contactRef, cursor);
+                if (columnIndex != -1) {
+                    final long contactId = cursor.getLong(columnIndex);
+                    if (contactId != 0 && !Contacts.isEnterpriseContactId(contactId)) {
+                        info.contactIdOrZero = contactId;
+                        if (VDBG) {
+                            Rlog.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
+                        }
+                    }
+                    if (Contacts.isEnterpriseContactId(contactId)) {
+                        info.userType = USER_TYPE_WORK;
+                    }
+                } else {
+                    // No valid columnIndex, so we can't look up person_id.
+                    Rlog.w(TAG, "Couldn't find contact_id column for " + contactRef);
+                    // Watch out: this means that anything that depends on
+                    // person_id will be broken (like contact photo lookups in
+                    // the in-call UI, for example.)
+                }
+
+                // Contact lookupKey
+                columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
+                if (columnIndex != -1) {
+                    info.lookupKey = cursor.getString(columnIndex);
+                }
+
+                // Display photo URI.
+                columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+                    info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
+                } else {
+                    info.contactDisplayPhotoUri = null;
+                }
+
+                columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME);
+                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+                    info.preferredPhoneAccountComponent =
+                            ComponentName.unflattenFromString(cursor.getString(columnIndex));
+                }
+
+                columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_ID);
+                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+                    info.preferredPhoneAccountId = cursor.getString(columnIndex);
+                }
+
+                // look for the custom ringtone, create from the string stored
+                // in the database.
+                // An empty string ("") in the database indicates a silent ringtone,
+                // and we set contactRingtoneUri = Uri.EMPTY, so that no ringtone will be played.
+                // {null} in the database indicates the default ringtone,
+                // and we set contactRingtoneUri = null, so that default ringtone will be played.
+                columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
+                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+                    if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
+                        info.contactRingtoneUri = Uri.EMPTY;
+                    } else {
+                        info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
+                    }
+                } else {
+                    info.contactRingtoneUri = null;
+                }
+
+                // look for the send to voicemail flag, set it to true only
+                // under certain circumstances.
+                columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
+                info.shouldSendToVoicemail = (columnIndex != -1) &&
+                        ((cursor.getInt(columnIndex)) == 1);
+                info.contactExists = true;
+            }
+            cursor.close();
+            cursor = null;
+        }
+
+        info.needUpdate = false;
+        info.name = normalize(info.name);
+        info.contactRefUri = contactRef;
+
+        return info;
+    }
+
+    /**
+     * getCallerInfo given a URI, look up in the call-log database
+     * for the uri unique key.
+     * @param context the context used to get the ContentResolver
+     * @param contactRef the URI used to lookup caller id
+     * @return the CallerInfo which contains the caller id for the given
+     * number. The returned CallerInfo is null if no number is supplied.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
+        CallerInfo info = null;
+        ContentResolver cr = CallerInfoAsyncQuery.getCurrentProfileContentResolver(context);
+        if (cr != null) {
+            try {
+                info = getCallerInfo(context, contactRef,
+                        cr.query(contactRef, null, null, null, null));
+            } catch (RuntimeException re) {
+                Rlog.e(TAG, "Error getting caller info.", re);
+            }
+        }
+        return info;
+    }
+
+    /**
+     * getCallerInfo given a phone number, look up in the call-log database
+     * for the matching caller id info.
+     * @param context the context used to get the ContentResolver
+     * @param number the phone number used to lookup caller id
+     * @return the CallerInfo which contains the caller id for the given
+     * number. The returned CallerInfo is null if no number is supplied. If
+     * a matching number is not found, then a generic caller info is returned,
+     * with all relevant fields empty or null.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static CallerInfo getCallerInfo(Context context, String number) {
+        if (VDBG) Rlog.v(TAG, "getCallerInfo() based on number...");
+
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        return getCallerInfo(context, number, subId);
+    }
+
+    /**
+     * getCallerInfo given a phone number and subscription, look up in the call-log database
+     * for the matching caller id info.
+     * @param context the context used to get the ContentResolver
+     * @param number the phone number used to lookup caller id
+     * @param subId the subscription for checking for if voice mail number or not
+     * @return the CallerInfo which contains the caller id for the given
+     * number. The returned CallerInfo is null if no number is supplied. If
+     * a matching number is not found, then a generic caller info is returned,
+     * with all relevant fields empty or null.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static CallerInfo getCallerInfo(Context context, String number, int subId) {
+
+        if (TextUtils.isEmpty(number)) {
+            return null;
+        }
+
+        // Change the callerInfo number ONLY if it is an emergency number
+        // or if it is the voicemail number.  If it is either, take a
+        // shortcut and skip the query.
+        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+            return new CallerInfo().markAsEmergency(context);
+        } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
+            return new CallerInfo().markAsVoiceMail();
+        }
+
+        Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+                Uri.encode(number));
+
+        CallerInfo info = getCallerInfo(context, contactUri);
+        info = doSecondaryLookupIfNecessary(context, number, info);
+
+        // if no query results were returned with a viable number,
+        // fill in the original number value we used to query with.
+        if (TextUtils.isEmpty(info.phoneNumber)) {
+            info.phoneNumber = number;
+        }
+
+        return info;
+    }
+
+    /**
+     * @return Name assocaited with this caller.
+     */
+    @Nullable
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set caller Info Name.
+     * @param name caller Info Name
+     *
+     * @hide
+     */
+    public void setName(@Nullable String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return Phone number assocaited with this caller.
+     */
+    @Nullable
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    /** @hide */
+    public void setPhoneNumber(String number) {
+        phoneNumber = number;
+    }
+
+    /**
+     * @return Contact ID, which will be 0 if a contact comes from the corp Contacts Provider.
+     */
+    public long getContactId() {
+      return contactIdOrZero;
+    }
+
+    /**
+     * @return Contact display photo URI. If a contact has no display photo but a thumbnail,
+     * it'll the thumbnail URI instead.
+     */
+    @Nullable
+    public Uri getContactDisplayPhotoUri() {
+      return contactDisplayPhotoUri;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public void SetContactDisplayPhotoUri(Uri photoUri) {
+        contactDisplayPhotoUri = photoUri;
+    }
+
+    /**
+     * Performs another lookup if previous lookup fails and it's a SIP call
+     * and the peer's username is all numeric. Look up the username as it
+     * could be a PSTN number in the contact database.
+     *
+     * @param context the query context
+     * @param number the original phone number, could be a SIP URI
+     * @param previousResult the result of previous lookup
+     * @return previousResult if it's not the case
+     */
+    static CallerInfo doSecondaryLookupIfNecessary(Context context,
+            String number, CallerInfo previousResult) {
+        if (!previousResult.contactExists
+                && PhoneNumberUtils.isUriNumber(number)) {
+            String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
+            if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
+                previousResult = getCallerInfo(context,
+                        Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+                                Uri.encode(username)));
+            }
+        }
+        return previousResult;
+    }
+
+    // Accessors
+
+    /**
+     * @return true if the caller info is an emergency number.
+     * @hide
+     */
+    public boolean isEmergencyNumber() {
+        return mIsEmergency;
+    }
+
+    /**
+     * @return true if the caller info is a voicemail number.
+     * @hide
+     */
+    public boolean isVoiceMailNumber() {
+        return mIsVoiceMail;
+    }
+
+    /**
+     * Mark this CallerInfo as an emergency call.
+     * @param context To lookup the localized 'Emergency Number' string.
+     * @return this instance.
+     */
+    // TODO: Note we're setting the phone number here (refer to
+    // javadoc comments at the top of CallerInfo class) to a localized
+    // string 'Emergency Number'. This is pretty bad because we are
+    // making UI work here instead of just packaging the data. We
+    // should set the phone number to the dialed number and name to
+    // 'Emergency Number' and let the UI make the decision about what
+    // should be displayed.
+    /* package */ CallerInfo markAsEmergency(Context context) {
+        phoneNumber = context.getString(
+            com.android.internal.R.string.emergency_call_dialog_number_for_display);
+        photoResource = com.android.internal.R.drawable.picture_emergency;
+        mIsEmergency = true;
+        return this;
+    }
+
+
+    /**
+     * Mark this CallerInfo as a voicemail call. The voicemail label
+     * is obtained from the telephony manager. Caller must hold the
+     * READ_PHONE_STATE permission otherwise the phoneNumber will be
+     * set to null.
+     * @return this instance.
+     */
+    // TODO: As in the emergency number handling, we end up writing a
+    // string in the phone number field.
+    /* package */ CallerInfo markAsVoiceMail() {
+
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        return markAsVoiceMail(subId);
+
+    }
+
+    /* package */ CallerInfo markAsVoiceMail(int subId) {
+        mIsVoiceMail = true;
+
+        try {
+            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag(subId);
+
+            phoneNumber = voiceMailLabel;
+        } catch (SecurityException se) {
+            // Should never happen: if this process does not have
+            // permission to retrieve VM tag, it should not have
+            // permission to retrieve VM number and would not call
+            // this method.
+            // Leave phoneNumber untouched.
+            Rlog.e(TAG, "Cannot access VoiceMail.", se);
+        }
+        // TODO: There is no voicemail picture?
+        // FIXME: FIND ANOTHER ICON
+        // photoResource = android.R.drawable.badge_voicemail;
+        return this;
+    }
+
+    private static String normalize(String s) {
+        if (s == null || s.length() > 0) {
+            return s;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the column index to use to find the "person_id" field in
+     * the specified cursor, based on the contact URI that was originally
+     * queried.
+     *
+     * This is a helper function for the getCallerInfo() method that takes
+     * a Cursor.  Looking up the person_id is nontrivial (compared to all
+     * the other CallerInfo fields) since the column we need to use
+     * depends on what query we originally ran.
+     *
+     * Watch out: be sure to not do any database access in this method, since
+     * it's run from the UI thread (see comments below for more info.)
+     *
+     * @return the columnIndex to use (with cursor.getLong()) to get the
+     * person_id, or -1 if we couldn't figure out what colum to use.
+     *
+     * TODO: Add a unittest for this method.  (This is a little tricky to
+     * test, since we'll need a live contacts database to test against,
+     * preloaded with at least some phone numbers and SIP addresses.  And
+     * we'll probably have to hardcode the column indexes we expect, so
+     * the test might break whenever the contacts schema changes.  But we
+     * can at least make sure we handle all the URI patterns we claim to,
+     * and that the mime types match what we expect...)
+     */
+    private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
+        // TODO: This is pretty ugly now, see bug 2269240 for
+        // more details. The column to use depends upon the type of URL:
+        // - content://com.android.contacts/data/phones ==> use the "contact_id" column
+        // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
+        // - content://com.android.contacts/data ==> use the "contact_id" column
+        // If it's none of the above, we leave columnIndex=-1 which means
+        // that the person_id field will be left unset.
+        //
+        // The logic here *used* to be based on the mime type of contactRef
+        // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
+        // RawContacts.CONTACT_ID column).  But looking up the mime type requires
+        // a call to context.getContentResolver().getType(contactRef), which
+        // isn't safe to do from the UI thread since it can cause an ANR if
+        // the contacts provider is slow or blocked (like during a sync.)
+        //
+        // So instead, figure out the column to use for person_id by just
+        // looking at the URI itself.
+
+        if (VDBG) Rlog.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
+                        + contactRef + "'...");
+        // Warning: Do not enable the following logging (due to ANR risk.)
+        // if (VDBG) Rlog.v(TAG, "- MIME type: "
+        //                 + context.getContentResolver().getType(contactRef));
+
+        String url = contactRef.toString();
+        String columnName = null;
+        if (url.startsWith("content://com.android.contacts/data/phones")) {
+            // Direct lookup in the Phone table.
+            // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
+            if (VDBG) Rlog.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
+            columnName = RawContacts.CONTACT_ID;
+        } else if (url.startsWith("content://com.android.contacts/data")) {
+            // Direct lookup in the Data table.
+            // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
+            if (VDBG) Rlog.v(TAG, "'data' URI; using Data.CONTACT_ID");
+            // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
+            columnName = Data.CONTACT_ID;
+        } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
+            // Lookup in the PhoneLookup table, which provides "fuzzy matching"
+            // for phone numbers.
+            // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
+            if (VDBG) Rlog.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
+            columnName = PhoneLookup._ID;
+        } else {
+            Rlog.w(TAG, "Unexpected prefix for contactRef '" + url + "'");
+        }
+        int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
+        if (VDBG) Rlog.v(TAG, "==> Using column '" + columnName
+                        + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
+        return columnIndex;
+    }
+
+    /**
+     * Updates this CallerInfo's geoDescription field, based on the raw
+     * phone number in the phoneNumber field.
+     *
+     * (Note that the various getCallerInfo() methods do *not* set the
+     * geoDescription automatically; you need to call this method
+     * explicitly to get it.)
+     *
+     * @param context the context used to look up the current locale / country
+     * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
+     *        this specifies a fallback number to use instead.
+     * @hide
+     */
+    public void updateGeoDescription(Context context, String fallbackNumber) {
+        String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
+        geoDescription = getGeoDescription(context, number);
+    }
+
+    /**
+     * @return a geographical description string for the specified number.
+     * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
+     *
+     * @hide
+     */
+    public static String getGeoDescription(Context context, String number) {
+        if (VDBG) Rlog.v(TAG, "getGeoDescription('" + number + "')...");
+
+        if (TextUtils.isEmpty(number)) {
+            return null;
+        }
+
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
+
+        Locale locale = context.getResources().getConfiguration().locale;
+        String countryIso = getCurrentCountryIso(context, locale);
+        PhoneNumber pn = null;
+        try {
+            if (VDBG) Rlog.v(TAG, "parsing '" + number
+                            + "' for countryIso '" + countryIso + "'...");
+            pn = util.parse(number, countryIso);
+            if (VDBG) Rlog.v(TAG, "- parsed number: " + pn);
+        } catch (NumberParseException e) {
+            Rlog.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
+                    + Rlog.pii(TAG, number) + "'");
+        }
+
+        if (pn != null) {
+            String description = geocoder.getDescriptionForNumber(pn, locale);
+            if (VDBG) Rlog.v(TAG, "- got description: '" + description + "'");
+            return description;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return The ISO 3166-1 two letters country code of the country the user
+     *         is in.
+     */
+    private static String getCurrentCountryIso(Context context, Locale locale) {
+        String countryIso = null;
+        CountryDetector detector = (CountryDetector) context.getSystemService(
+                Context.COUNTRY_DETECTOR);
+        if (detector != null) {
+            Country country = detector.detectCountry();
+            if (country != null) {
+                countryIso = country.getCountryIso();
+            } else {
+                Rlog.e(TAG, "CountryDetector.detectCountry() returned null.");
+            }
+        }
+        if (countryIso == null) {
+            countryIso = locale.getCountry();
+            Rlog.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
+                    + countryIso);
+        }
+        return countryIso;
+    }
+
+    /** @hide */
+    protected static String getCurrentCountryIso(Context context) {
+        return getCurrentCountryIso(context, Locale.getDefault());
+    }
+
+    /**
+     * @return a string debug representation of this instance.
+     */
+    @Override
+    public String toString() {
+        // Warning: never check in this file with VERBOSE_DEBUG = true
+        // because that will result in PII in the system log.
+        final boolean VERBOSE_DEBUG = false;
+
+        if (VERBOSE_DEBUG) {
+            return new StringBuilder(384)
+                    .append(super.toString() + " { ")
+                    .append("\nname: " + name)
+                    .append("\nphoneNumber: " + phoneNumber)
+                    .append("\nnormalizedNumber: " + normalizedNumber)
+                    .append("\ngeoDescription: " + geoDescription)
+                    .append("\ncnapName: " + cnapName)
+                    .append("\nnumberPresentation: " + numberPresentation)
+                    .append("\nnamePresentation: " + namePresentation)
+                    .append("\ncontactExits: " + contactExists)
+                    .append("\nphoneLabel: " + phoneLabel)
+                    .append("\nnumberType: " + numberType)
+                    .append("\nnumberLabel: " + numberLabel)
+                    .append("\nphotoResource: " + photoResource)
+                    .append("\ncontactIdOrZero: " + contactIdOrZero)
+                    .append("\nneedUpdate: " + needUpdate)
+                    .append("\ncontactRingtoneUri: " + contactRingtoneUri)
+                    .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
+                    .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
+                    .append("\ncachedPhoto: " + cachedPhoto)
+                    .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
+                    .append("\nemergency: " + mIsEmergency)
+                    .append("\nvoicemail " + mIsVoiceMail)
+                    .append("\ncontactExists " + contactExists)
+                    .append("\nuserType " + userType)
+                    .append(" }")
+                    .toString();
+        } else {
+            return new StringBuilder(128)
+                    .append(super.toString() + " { ")
+                    .append("name " + ((name == null) ? "null" : "non-null"))
+                    .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
+                    .append(" }")
+                    .toString();
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/CallerInfoAsyncQuery.java b/telephony/java/android/telephony/CallerInfoAsyncQuery.java
new file mode 100644
index 0000000..88b471e
--- /dev/null
+++ b/telephony/java/android/telephony/CallerInfoAsyncQuery.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.app.ActivityManager;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract.PhoneLookup;
+import android.text.TextUtils;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to make it easier to run asynchronous caller-id lookup queries.
+ * @see CallerInfo
+ *
+ * {@hide}
+ */
+public class CallerInfoAsyncQuery {
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "CallerInfoAsyncQuery";
+
+    private static final int EVENT_NEW_QUERY = 1;
+    private static final int EVENT_ADD_LISTENER = 2;
+    private static final int EVENT_END_OF_QUEUE = 3;
+    private static final int EVENT_EMERGENCY_NUMBER = 4;
+    private static final int EVENT_VOICEMAIL_NUMBER = 5;
+    private static final int EVENT_GET_GEO_DESCRIPTION = 6;
+
+    private CallerInfoAsyncQueryHandler mHandler;
+
+    // If the CallerInfo query finds no contacts, should we use the
+    // PhoneNumberOfflineGeocoder to look up a "geo description"?
+    // (TODO: This could become a flag in config.xml if it ever needs to be
+    // configured on a per-product basis.)
+    private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = true;
+
+    /**
+     * Interface for a CallerInfoAsyncQueryHandler result return.
+     */
+    public interface OnQueryCompleteListener {
+        /**
+         * Called when the query is complete.
+         */
+        public void onQueryComplete(int token, Object cookie, CallerInfo ci);
+    }
+
+
+    /**
+     * Wrap the cookie from the WorkerArgs with additional information needed by our
+     * classes.
+     */
+    private static final class CookieWrapper {
+        @UnsupportedAppUsage
+        private CookieWrapper() {
+        }
+
+        public OnQueryCompleteListener listener;
+        public Object cookie;
+        public int event;
+        public String number;
+        public String geoDescription;
+
+        public int subId;
+    }
+
+
+    /**
+     * Simple exception used to communicate problems with the query pool.
+     */
+    public static class QueryPoolException extends SQLException {
+        public QueryPoolException(String error) {
+            super(error);
+        }
+    }
+
+    /**
+     * @return {@link ContentResolver} for the "current" user.
+     */
+    static ContentResolver getCurrentProfileContentResolver(Context context) {
+
+        if (DBG) Rlog.d(LOG_TAG, "Trying to get current content resolver...");
+
+        final int currentUser = ActivityManager.getCurrentUser();
+        final int myUser = UserManager.get(context).getUserHandle();
+
+        if (DBG) Rlog.d(LOG_TAG, "myUser=" + myUser + "currentUser=" + currentUser);
+
+        if (myUser != currentUser) {
+            final Context otherContext;
+            try {
+                otherContext = context.createPackageContextAsUser(context.getPackageName(),
+                        /* flags =*/ 0, UserHandle.of(currentUser));
+                return otherContext.getContentResolver();
+            } catch (NameNotFoundException e) {
+                Rlog.e(LOG_TAG, "Can't find self package", e);
+                // Fall back to the primary user.
+            }
+        }
+        return context.getContentResolver();
+    }
+
+    /**
+     * Our own implementation of the AsyncQueryHandler.
+     */
+    private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler {
+
+        /*
+         * The information relevant to each CallerInfo query.  Each query may have multiple
+         * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper
+         * objects in the queue (one with a new query event, and one with a end event, with
+         * 0 or more additional listeners in between).
+         */
+
+        /**
+         * Context passed by the caller.
+         *
+         * NOTE: The actual context we use for query may *not* be this context; since we query
+         * against the "current" contacts provider.  In the constructor we pass the "current"
+         * context resolver (obtained via {@link #getCurrentProfileContentResolver) and pass it
+         * to the super class.
+         */
+        private Context mContext;
+        private Uri mQueryUri;
+        private CallerInfo mCallerInfo;
+        private List<Runnable> mPendingListenerCallbacks = new ArrayList<>();
+
+        /**
+         * Our own query worker thread.
+         *
+         * This thread handles the messages enqueued in the looper.  The normal sequence
+         * of events is that a new query shows up in the looper queue, followed by 0 or
+         * more add listener requests, and then an end request.  Of course, these requests
+         * can be interlaced with requests from other tokens, but is irrelevant to this
+         * handler since the handler has no state.
+         *
+         * Note that we depend on the queue to keep things in order; in other words, the
+         * looper queue must be FIFO with respect to input from the synchronous startQuery
+         * calls and output to this handleMessage call.
+         *
+         * This use of the queue is required because CallerInfo objects may be accessed
+         * multiple times before the query is complete.  All accesses (listeners) must be
+         * queued up and informed in order when the query is complete.
+         */
+        protected class CallerInfoWorkerHandler extends WorkerHandler {
+            public CallerInfoWorkerHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                WorkerArgs args = (WorkerArgs) msg.obj;
+                CookieWrapper cw = (CookieWrapper) args.cookie;
+
+                if (cw == null) {
+                    // Normally, this should never be the case for calls originating
+                    // from within this code.
+                    // However, if there is any code that this Handler calls (such as in
+                    // super.handleMessage) that DOES place unexpected messages on the
+                    // queue, then we need pass these messages on.
+                    Rlog.i(LOG_TAG, "Unexpected command (CookieWrapper is null): " + msg.what +
+                            " ignored by CallerInfoWorkerHandler, passing onto parent.");
+
+                    super.handleMessage(msg);
+                } else {
+
+                    Rlog.d(LOG_TAG, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
+                        " command: " + msg.what + " query URI: " + sanitizeUriToString(args.uri));
+
+                    switch (cw.event) {
+                        case EVENT_NEW_QUERY:
+                            //start the sql command.
+                            super.handleMessage(msg);
+                            break;
+
+                        // shortcuts to avoid query for recognized numbers.
+                        case EVENT_EMERGENCY_NUMBER:
+                        case EVENT_VOICEMAIL_NUMBER:
+
+                        case EVENT_ADD_LISTENER:
+                        case EVENT_END_OF_QUEUE:
+                            // query was already completed, so just send the reply.
+                            // passing the original token value back to the caller
+                            // on top of the event values in arg1.
+                            Message reply = args.handler.obtainMessage(msg.what);
+                            reply.obj = args;
+                            reply.arg1 = msg.arg1;
+
+                            reply.sendToTarget();
+
+                            break;
+                        case EVENT_GET_GEO_DESCRIPTION:
+                            handleGeoDescription(msg);
+                            break;
+                        default:
+                    }
+                }
+            }
+
+            private void handleGeoDescription(Message msg) {
+                WorkerArgs args = (WorkerArgs) msg.obj;
+                CookieWrapper cw = (CookieWrapper) args.cookie;
+                if (!TextUtils.isEmpty(cw.number) && cw.cookie != null && mContext != null) {
+                    final long startTimeMillis = SystemClock.elapsedRealtime();
+                    cw.geoDescription = CallerInfo.getGeoDescription(mContext, cw.number);
+                    final long duration = SystemClock.elapsedRealtime() - startTimeMillis;
+                    if (duration > 500) {
+                        if (DBG) Rlog.d(LOG_TAG, "[handleGeoDescription]" +
+                                "Spends long time to retrieve Geo description: " + duration);
+                    }
+                }
+                Message reply = args.handler.obtainMessage(msg.what);
+                reply.obj = args;
+                reply.arg1 = msg.arg1;
+                reply.sendToTarget();
+            }
+        }
+
+
+        /**
+         * Asynchronous query handler class for the contact / callerinfo object.
+         */
+        private CallerInfoAsyncQueryHandler(Context context) {
+            super(getCurrentProfileContentResolver(context));
+            mContext = context;
+        }
+
+        @Override
+        protected Handler createHandler(Looper looper) {
+            return new CallerInfoWorkerHandler(looper);
+        }
+
+        /**
+         * Overrides onQueryComplete from AsyncQueryHandler.
+         *
+         * This method takes into account the state of this class; we construct the CallerInfo
+         * object only once for each set of listeners. When the query thread has done its work
+         * and calls this method, we inform the remaining listeners in the queue, until we're
+         * out of listeners.  Once we get the message indicating that we should expect no new
+         * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the
+         * pool.
+         */
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            Rlog.d(LOG_TAG, "##### onQueryComplete() #####   query complete for token: " + token);
+
+            //get the cookie and notify the listener.
+            CookieWrapper cw = (CookieWrapper) cookie;
+            if (cw == null) {
+                // Normally, this should never be the case for calls originating
+                // from within this code.
+                // However, if there is any code that calls this method, we should
+                // check the parameters to make sure they're viable.
+                Rlog.i(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
+                if (cursor != null) {
+                    cursor.close();
+                }
+                return;
+            }
+
+            if (cw.event == EVENT_END_OF_QUEUE) {
+                for (Runnable r : mPendingListenerCallbacks) {
+                    r.run();
+                }
+                mPendingListenerCallbacks.clear();
+
+                release();
+                if (cursor != null) {
+                    cursor.close();
+                }
+                return;
+            }
+
+            // If the cw.event == EVENT_GET_GEO_DESCRIPTION, means it would not be the 1st
+            // time entering the onQueryComplete(), mCallerInfo should not be null.
+            if (cw.event == EVENT_GET_GEO_DESCRIPTION) {
+                if (mCallerInfo != null) {
+                    mCallerInfo.geoDescription = cw.geoDescription;
+                }
+                // notify that we can clean up the queue after this.
+                CookieWrapper endMarker = new CookieWrapper();
+                endMarker.event = EVENT_END_OF_QUEUE;
+                startQuery(token, endMarker, null, null, null, null, null);
+            }
+
+            // check the token and if needed, create the callerinfo object.
+            if (mCallerInfo == null) {
+                if ((mContext == null) || (mQueryUri == null)) {
+                    throw new QueryPoolException
+                            ("Bad context or query uri, or CallerInfoAsyncQuery already released.");
+                }
+
+                // adjust the callerInfo data as needed, and only if it was set from the
+                // initial query request.
+                // Change the callerInfo number ONLY if it is an emergency number or the
+                // voicemail number, and adjust other data (including photoResource)
+                // accordingly.
+                if (cw.event == EVENT_EMERGENCY_NUMBER) {
+                    // Note we're setting the phone number here (refer to javadoc
+                    // comments at the top of CallerInfo class).
+                    mCallerInfo = new CallerInfo().markAsEmergency(mContext);
+                } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
+                    mCallerInfo = new CallerInfo().markAsVoiceMail(cw.subId);
+                } else {
+                    mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor);
+                    if (DBG) Rlog.d(LOG_TAG, "==> Got mCallerInfo: " + mCallerInfo);
+
+                    CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary(
+                            mContext, cw.number, mCallerInfo);
+                    if (newCallerInfo != mCallerInfo) {
+                        mCallerInfo = newCallerInfo;
+                        if (DBG) Rlog.d(LOG_TAG, "#####async contact look up with numeric username"
+                                + mCallerInfo);
+                    }
+
+                    // Use the number entered by the user for display.
+                    if (!TextUtils.isEmpty(cw.number)) {
+                        mCallerInfo.setPhoneNumber(PhoneNumberUtils.formatNumber(cw.number,
+                                mCallerInfo.normalizedNumber,
+                                CallerInfo.getCurrentCountryIso(mContext)));
+                    }
+
+                    // This condition refer to the google default code for geo.
+                    // If the number exists in Contacts, the CallCard would never show
+                    // the geo description, so it would be unnecessary to query it.
+                    if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {
+                        if (TextUtils.isEmpty(mCallerInfo.getName())) {
+                            if (DBG) Rlog.d(LOG_TAG, "start querying geo description");
+                            cw.event = EVENT_GET_GEO_DESCRIPTION;
+                            startQuery(token, cw, null, null, null, null, null);
+                            return;
+                        }
+                    }
+                }
+
+                if (DBG) Rlog.d(LOG_TAG, "constructing CallerInfo object for token: " + token);
+
+                //notify that we can clean up the queue after this.
+                CookieWrapper endMarker = new CookieWrapper();
+                endMarker.event = EVENT_END_OF_QUEUE;
+                startQuery(token, endMarker, null, null, null, null, null);
+            }
+
+            //notify the listener that the query is complete.
+            if (cw.listener != null) {
+                mPendingListenerCallbacks.add(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (DBG) Rlog.d(LOG_TAG, "notifying listener: "
+                                + cw.listener.getClass().toString() + " for token: " + token
+                                + mCallerInfo);
+                        cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
+                    }
+                });
+            } else {
+                Rlog.w(LOG_TAG, "There is no listener to notify for this query.");
+            }
+
+            if (cursor != null) {
+               cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Private constructor for factory methods.
+     */
+    private CallerInfoAsyncQuery() {
+    }
+
+
+    /**
+     * Factory method to start query with a Uri query spec
+     */
+    public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef,
+            OnQueryCompleteListener listener, Object cookie) {
+
+        CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
+        c.allocate(context, contactRef);
+
+        if (DBG) Rlog.d(LOG_TAG, "starting query for URI: " + contactRef + " handler: " + c.toString());
+
+        //create cookieWrapper, start query
+        CookieWrapper cw = new CookieWrapper();
+        cw.listener = listener;
+        cw.cookie = cookie;
+        cw.event = EVENT_NEW_QUERY;
+
+        c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);
+
+        return c;
+    }
+
+    /**
+     * Factory method to start the query based on a number.
+     *
+     * Note: if the number contains an "@" character we treat it
+     * as a SIP address, and look it up directly in the Data table
+     * rather than using the PhoneLookup table.
+     * TODO: But eventually we should expose two separate methods, one for
+     * numbers and one for SIP addresses, and then have
+     * PhoneUtils.startGetCallerInfo() decide which one to call based on
+     * the phone type of the incoming connection.
+     */
+    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+            OnQueryCompleteListener listener, Object cookie) {
+
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        return startQuery(token, context, number, listener, cookie, subId);
+    }
+
+    /**
+     * Factory method to start the query based on a number with specific subscription.
+     *
+     * Note: if the number contains an "@" character we treat it
+     * as a SIP address, and look it up directly in the Data table
+     * rather than using the PhoneLookup table.
+     * TODO: But eventually we should expose two separate methods, one for
+     * numbers and one for SIP addresses, and then have
+     * PhoneUtils.startGetCallerInfo() decide which one to call based on
+     * the phone type of the incoming connection.
+     */
+    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
+            OnQueryCompleteListener listener, Object cookie, int subId) {
+
+        if (DBG) {
+            Rlog.d(LOG_TAG, "##### CallerInfoAsyncQuery startQuery()... #####");
+            Rlog.d(LOG_TAG, "- number: " + /*number*/ "xxxxxxx");
+            Rlog.d(LOG_TAG, "- cookie: " + cookie);
+        }
+
+        // Construct the URI object and query params, and start the query.
+
+        final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath(number)
+                .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
+                        String.valueOf(PhoneNumberUtils.isUriNumber(number)))
+                .build();
+
+        if (DBG) {
+            Rlog.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
+        }
+
+        CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
+        c.allocate(context, contactRef);
+
+        //create cookieWrapper, start query
+        CookieWrapper cw = new CookieWrapper();
+        cw.listener = listener;
+        cw.cookie = cookie;
+        cw.number = number;
+        cw.subId = subId;
+
+        // check to see if these are recognized numbers, and use shortcuts if we can.
+        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+            cw.event = EVENT_EMERGENCY_NUMBER;
+        } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
+            cw.event = EVENT_VOICEMAIL_NUMBER;
+        } else {
+            cw.event = EVENT_NEW_QUERY;
+        }
+
+        c.mHandler.startQuery(token,
+                              cw,  // cookie
+                              contactRef,  // uri
+                              null,  // projection
+                              null,  // selection
+                              null,  // selectionArgs
+                              null);  // orderBy
+        return c;
+    }
+
+    /**
+     * Method to add listeners to a currently running query
+     */
+    public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {
+
+        if (DBG) Rlog.d(LOG_TAG, "adding listener to query: " + sanitizeUriToString(mHandler.mQueryUri) +
+                " handler: " + mHandler.toString());
+
+        //create cookieWrapper, add query request to end of queue.
+        CookieWrapper cw = new CookieWrapper();
+        cw.listener = listener;
+        cw.cookie = cookie;
+        cw.event = EVENT_ADD_LISTENER;
+
+        mHandler.startQuery(token, cw, null, null, null, null, null);
+    }
+
+    /**
+     * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct
+     * state of context and uri.
+     */
+    private void allocate(Context context, Uri contactRef) {
+        if ((context == null) || (contactRef == null)){
+            throw new QueryPoolException("Bad context or query uri.");
+        }
+        mHandler = new CallerInfoAsyncQueryHandler(context);
+        mHandler.mQueryUri = contactRef;
+    }
+
+    /**
+     * Releases the relevant data.
+     */
+    @UnsupportedAppUsage
+    private void release() {
+        mHandler.mContext = null;
+        mHandler.mQueryUri = null;
+        mHandler.mCallerInfo = null;
+        mHandler = null;
+    }
+
+    private static String sanitizeUriToString(Uri uri) {
+        if (uri != null) {
+            String uriString = uri.toString();
+            int indexOfLastSlash = uriString.lastIndexOf('/');
+            if (indexOfLastSlash > 0) {
+                return uriString.substring(0, indexOfLastSlash) + "/xxxxxxx";
+            } else {
+                return uriString;
+            }
+        } else {
+            return "";
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 654b54d..b449578 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -108,6 +108,19 @@
             "call_forwarding_visibility_bool";
 
     /**
+     * Boolean indicating if carrier supports call forwarding option "When unreachable".
+     *
+     * {@code true}: Call forwarding option "When unreachable" is supported.
+     * {@code false}: Call forwarding option "When unreachable" is not supported. Option will be
+     * greyed out in the UI.
+     *
+     * By default this value is true.
+     * @hide
+     */
+    public static final String KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL =
+            "call_forwarding_when_unreachable_supported_bool";
+
+    /**
      * Boolean indicating if the "Caller ID" item is visible in the Additional Settings menu.
      * true means visible. false means gone.
      * @hide
@@ -2791,6 +2804,21 @@
             "opportunistic_network_data_switch_exit_hysteresis_time_long";
 
     /**
+     * Controls whether to do ping test before switching data to opportunistic network.
+     * This carrier config is used to disable this feature.
+     * @hide
+     */
+    public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL =
+            "ping_test_before_data_switch_bool";
+
+    /**
+     * Controls time in milli seconds until DcTracker reevaluates 5G connection state.
+     * @hide
+     */
+    public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
+            "5g_watchdog_time_long";
+
+    /**
      * Indicates zero or more emergency number prefix(es), because some carrier requires
      * if users dial an emergency number address with a specific prefix, the combination of the
      * prefix and the address is also a valid emergency number to dial. For example, an emergency
@@ -3235,6 +3263,7 @@
         sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL, true);
         sDefaults.putBoolean(KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL, true);
         sDefaults.putBoolean(KEY_CALL_FORWARDING_VISIBILITY_BOOL, true);
+        sDefaults.putBoolean(KEY_CALL_FORWARDING_WHEN_UNREACHABLE_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
@@ -3565,6 +3594,9 @@
         sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG, 10000);
         /* Default value is 3 seconds. */
         sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 3000);
+        sDefaults.putBoolean(KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL, true);
+        /* Default value is 1 hour. */
+        sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000);
         sDefaults.putAll(Gps.getDefaults());
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
@@ -3799,7 +3831,7 @@
      * @see #getConfigForSubId
      */
     @Nullable
-    public PersistableBundle getConfigByComponentForSubId(String prefix, int subId) {
+    public PersistableBundle getConfigByComponentForSubId(@NonNull String prefix, int subId) {
         PersistableBundle configs = getConfigForSubId(subId);
 
         if (configs == null) {
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 950ae6c..c138018 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -323,6 +323,7 @@
         }
     };
 
+    @NonNull
     @Override
     public String toString() {
         return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 3dd9318..407ced7 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -134,6 +135,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder().append(this.getClass().getName())
@@ -155,7 +157,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof DataSpecificRegistrationInfo)) return false;
diff --git a/telephony/java/android/telephony/LteVopsSupportInfo.java b/telephony/java/android/telephony/LteVopsSupportInfo.java
index ec9f078..7994c1b 100644
--- a/telephony/java/android/telephony/LteVopsSupportInfo.java
+++ b/telephony/java/android/telephony/LteVopsSupportInfo.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -94,7 +96,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (o == null || !(o instanceof LteVopsSupportInfo)) {
             return false;
         }
@@ -112,6 +114,7 @@
     /**
      * @return string representation.
      */
+    @NonNull
     @Override
     public String toString() {
         return ("LteVopsSupportInfo : "
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index a0af392..43bc85c 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -16,43 +16,65 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import android.os.SystemClock;
+import android.util.Range;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
- * Reports modem activity information
+ * Reports modem activity information.
  * @hide
  */
-public class ModemActivityInfo implements Parcelable {
+@SystemApi
+public final class ModemActivityInfo implements Parcelable {
     /**
-     * Tx power index
-     * index 0 = tx_power < 0dBm
-     * index 1 = 0dBm < tx_power < 5dBm
-     * index 2 = 5dBm < tx_power < 15dBm
-     * index 3 = 15dBm < tx_power < 20dBm
-     * index 4 = tx_power > 20dBm
+     * Tx(transmit) power level. see power index below
+     * <ul>
+     *   <li> index 0 = tx_power < 0dBm. </li>
+     *   <li> index 1 = 0dBm < tx_power < 5dBm. </li>
+     *   <li> index 2 = 5dBm < tx_power < 15dBm. </li>
+     *   <li> index 3 = 15dBm < tx_power < 20dBm. </li>
+     *   <li> index 4 = tx_power > 20dBm. </li>
+     * </ul>
      */
     public static final int TX_POWER_LEVELS = 5;
+    private static final Range<Integer>[] TX_POWER_RANGES = new Range[] {
+        new Range<>(Integer.MIN_VALUE, 0),
+        new Range<>(0, 5),
+        new Range<>(5, 15),
+        new Range<>(15, 20),
+        new Range<>(20, Integer.MAX_VALUE)
+
+    };
 
     private long mTimestamp;
     private int mSleepTimeMs;
     private int mIdleTimeMs;
-    private int [] mTxTimeMs = new int[TX_POWER_LEVELS];
+    private List<TransmitPower> mTransmitPowerInfo = new ArrayList<>(TX_POWER_LEVELS);
     private int mRxTimeMs;
-    private int mEnergyUsed;
 
     public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
-                        int[] txTimeMs, int rxTimeMs, int energyUsed) {
+                        @NonNull int[] txTimeMs, int rxTimeMs) {
         mTimestamp = timestamp;
         mSleepTimeMs = sleepTimeMs;
         mIdleTimeMs = idleTimeMs;
         if (txTimeMs != null) {
-            System.arraycopy(txTimeMs, 0, mTxTimeMs, 0, Math.min(txTimeMs.length, TX_POWER_LEVELS));
+            populateTransmitPowerRange(txTimeMs);
         }
         mRxTimeMs = rxTimeMs;
-        mEnergyUsed = energyUsed;
+    }
+
+    /** helper API to populate tx power range for each bucket **/
+    private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) {
+        for (int i = 0; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
+            mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i]));
+        }
     }
 
     @Override
@@ -61,9 +83,8 @@
             + " mTimestamp=" + mTimestamp
             + " mSleepTimeMs=" + mSleepTimeMs
             + " mIdleTimeMs=" + mIdleTimeMs
-            + " mTxTimeMs[]=" + Arrays.toString(mTxTimeMs)
+            + " mTransmitPowerInfo[]=" + mTransmitPowerInfo.toString()
             + " mRxTimeMs=" + mRxTimeMs
-            + " mEnergyUsed=" + mEnergyUsed
             + "}";
     }
 
@@ -82,9 +103,8 @@
                 txTimeMs[i] = in.readInt();
             }
             int rxTimeMs = in.readInt();
-            int energyUsed = in.readInt();
             return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs,
-                                txTimeMs, rxTimeMs, energyUsed);
+                                txTimeMs, rxTimeMs);
         }
 
         public ModemActivityInfo[] newArray(int size) {
@@ -97,102 +117,153 @@
         dest.writeInt(mSleepTimeMs);
         dest.writeInt(mIdleTimeMs);
         for (int i = 0; i < TX_POWER_LEVELS; i++) {
-            dest.writeInt(mTxTimeMs[i]);
+            dest.writeInt(mTransmitPowerInfo.get(i).getTimeInMillis());
         }
         dest.writeInt(mRxTimeMs);
-        dest.writeInt(mEnergyUsed);
     }
 
     /**
-     * @return timestamp of record creation
+     * @return milliseconds since boot, including mTimeInMillis spent in sleep.
+     * @see SystemClock#elapsedRealtime()
      */
     public long getTimestamp() {
         return mTimestamp;
     }
 
+    /** @hide */
     public void setTimestamp(long timestamp) {
         mTimestamp = timestamp;
     }
 
     /**
-     * @return tx time in ms. It's an array of tx times
-     * with each index...
+     * @return an arrayList of {@link TransmitPower} with each element representing the total time where
+     * transmitter is awake time (in ms) for a given power range (in dbm).
+     *
+     * @see #TX_POWER_LEVELS
      */
-    public int [] getTxTimeMillis() {
-        return mTxTimeMs;
+    @NonNull
+    public List<TransmitPower> getTransmitPowerInfo() {
+        return mTransmitPowerInfo;
     }
 
-    public void setTxTimeMillis(int[] txTimeMs) {
-        mTxTimeMs = txTimeMs;
+    /** @hide */
+    public void setTransmitTimeMillis(int[] txTimeMs) {
+        populateTransmitPowerRange(txTimeMs);
+    }
+
+    /** @hide */
+    @NonNull
+    public int[] getTransmitTimeMillis() {
+        int[] transmitTimeMillis = new int[TX_POWER_LEVELS];
+        for (int i = 0; i < transmitTimeMillis.length; i++) {
+            transmitTimeMillis[i] = mTransmitPowerInfo.get(i).getTimeInMillis();
+        }
+        return transmitTimeMillis;
     }
 
     /**
-     * @return sleep time in ms.
+     * @return total mTimeInMillis (in ms) when modem is in a low power or sleep state.
      */
     public int getSleepTimeMillis() {
         return mSleepTimeMs;
     }
 
+    /** @hide */
     public void setSleepTimeMillis(int sleepTimeMillis) {
         mSleepTimeMs = sleepTimeMillis;
     }
 
     /**
-     * @return idle time in ms.
+     * @return total mTimeInMillis (in ms) when modem is awake but neither the transmitter nor receiver are
+     * active.
      */
     public int getIdleTimeMillis() {
         return mIdleTimeMs;
     }
 
+    /** @hide */
     public void setIdleTimeMillis(int idleTimeMillis) {
         mIdleTimeMs = idleTimeMillis;
     }
 
     /**
-     * @return rx time in ms.
+     * @return rx(receive) mTimeInMillis in ms.
      */
-    public int getRxTimeMillis() {
+    public int getReceiveTimeMillis() {
         return mRxTimeMs;
     }
 
-    public void setRxTimeMillis(int rxTimeMillis) {
+    /** @hide */
+    public void setReceiveTimeMillis(int rxTimeMillis) {
         mRxTimeMs = rxTimeMillis;
     }
 
     /**
-     * product of current(mA), voltage(V) and time(ms)
-     * @return energy used
-     */
-    public int getEnergyUsed () {
-        return mEnergyUsed;
-    }
-
-    public void setEnergyUsed(int energyUsed) {
-        mEnergyUsed = energyUsed;
-    }
-
-    /**
-     * @return if the record is valid
+     * @return {@code true} if this {@link ModemActivityInfo} record is valid,
+     * {@code false} otherwise.
+     *
+     * @hide
      */
     public boolean isValid() {
-        for (int txVal : getTxTimeMillis()) {
-            if(txVal < 0) {
+        for (TransmitPower powerInfo : getTransmitPowerInfo()) {
+            if(powerInfo.getTimeInMillis() < 0) {
                 return false;
             }
         }
 
         return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
-                && (getRxTimeMillis() >= 0) && (getEnergyUsed() >= 0) && !isEmpty());
+                && (getReceiveTimeMillis() >= 0) && !isEmpty());
     }
 
     private boolean isEmpty() {
-        for (int txVal : getTxTimeMillis()) {
-            if(txVal != 0) {
+        for (TransmitPower txVal : getTransmitPowerInfo()) {
+            if(txVal.getTimeInMillis() != 0) {
                 return false;
             }
         }
 
         return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
-                && (getRxTimeMillis() == 0) && (getEnergyUsed() == 0));
+                && (getReceiveTimeMillis() == 0));
+    }
+
+    /**
+     * Transmit power Information, including the power range in dbm and the total time (in ms) where
+     * the transmitter is active/awake for this power range.
+     * e.g, range: 0dbm(lower) ~ 5dbm(upper)
+     *      time: 5ms
+     */
+    public class TransmitPower {
+        private int mTimeInMillis;
+        private Range<Integer> mPowerRangeInDbm;
+        /** @hide */
+        public TransmitPower(@NonNull Range<Integer> range, int time) {
+            this.mTimeInMillis = time;
+            this.mPowerRangeInDbm = range;
+        }
+
+        /**
+         * @return the total time in ms where the transmitter is active/wake for this power range
+         * {@link #getPowerRangeInDbm()}.
+         */
+        public int getTimeInMillis() {
+            return mTimeInMillis;
+        }
+
+        /**
+         * @return the power range in dbm. e.g, range: 0dbm(lower) ~ 5dbm(upper)
+         */
+        @NonNull
+        public Range<Integer> getPowerRangeInDbm() {
+            return mPowerRangeInDbm;
+        }
+
+        @Override
+        public String toString() {
+            return "TransmitPower{"
+                + " mTimeInMillis=" + mTimeInMillis
+                + " mPowerRangeInDbm={" + mPowerRangeInDbm.getLower()
+                + "," + mPowerRangeInDbm.getUpper()
+                + "}}";
+        }
     }
 }
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 2fae949..a76b8da 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -501,6 +501,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return new StringBuilder("NetworkRegistrationInfo{")
@@ -531,7 +532,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof NetworkRegistrationInfo)) {
diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java
index c35a485..e6f107e 100644
--- a/telephony/java/android/telephony/PhoneNumberRange.java
+++ b/telephony/java/android/telephony/PhoneNumberRange.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -104,7 +105,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         PhoneNumberRange that = (PhoneNumberRange) o;
@@ -119,6 +120,7 @@
         return Objects.hash(mCountryCode, mPrefix, mLowerBound, mUpperBound);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PhoneNumberRange{"
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 271195b..1ffed25 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -33,13 +33,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
 
+import dalvik.system.VMRuntime;
+
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
-import dalvik.system.VMRuntime;
-
 /**
  * A listener class for monitoring changes in specific telephony states
  * on the device, including service state, signal strength, message
@@ -301,11 +301,6 @@
      *  it could be the current active opportunistic subscription in use, or the
      *  subscription user selected as default data subscription in DSDS mode.
      *
-     *  Requires Permission: No permission is required to listen, but notification requires
-     *  {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or the calling
-     *  app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges})
-     *  on any active subscription.
-     *
      *  @see #onActiveDataSubscriptionIdChanged
      */
     public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000;
@@ -362,6 +357,30 @@
     @SystemApi
     public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES              = 0x08000000;
 
+    /**
+     * Listen for the emergency number placed from an outgoing call.
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
+     * @see #onOutgoingEmergencyCall
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+    public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER           = 0x10000000;
+
+    /**
+     * Listen for the emergency number placed from an outgoing SMS.
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
+     * @see #onOutgoingEmergencySms
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+    public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER            = 0x20000000;
+
     /*
      * Subscription used to listen to the phone state changes
      * @hide
@@ -844,6 +863,27 @@
     }
 
     /**
+     * Callback invoked when an outgoing call is placed to an emergency number.
+     *
+     * @param placedEmergencyNumber the emergency number {@link EmergencyNumber} the call is placed
+     *                              to.
+     * @hide
+     */
+    public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+        // default implementation empty
+    }
+
+    /**
+     * Callback invoked when an outgoing SMS is placed to an emergency number.
+     *
+     * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
+     * @hide
+     */
+    public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when OEM hook raw event is received on the registered subscription.
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
      * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
@@ -1151,7 +1191,6 @@
                             () -> psl.onPhysicalChannelConfigurationChanged(configs)));
         }
 
-        @Override
         public void onEmergencyNumberListChanged(Map emergencyNumberList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
@@ -1161,6 +1200,24 @@
                             () -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
         }
 
+        public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber)));
+        }
+
+        public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onOutgoingEmergencySms(sentEmergencyNumber)));
+        }
+
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 5fb9bac..701a375 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -17,6 +17,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -276,7 +278,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -294,6 +296,7 @@
                 && mPreciseDisconnectCause == other.mPreciseDisconnectCause);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index cd4fbac..90d443a 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -177,7 +177,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
 
         if (!(obj instanceof PreciseDataConnectionState)) {
             return false;
@@ -191,6 +191,7 @@
                 && mState == other.mState;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2651346..a985a6b 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -369,15 +369,16 @@
     /**
      * Create a new ServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
+     * This method is used by PhoneStateIntentReceiver, CellBroadcastReceiver, and maybe by
      * external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created ServiceState
      * @hide
      */
+    @NonNull
     @UnsupportedAppUsage
-    public static ServiceState newFromBundle(Bundle m) {
+    public static ServiceState newFromBundle(@NonNull Bundle m) {
         ServiceState ret;
         ret = new ServiceState();
         ret.setFromNotifierBundle(m);
diff --git a/telephony/java/android/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..2c10a09
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains CMAS (Commercial Mobile Alert System) warning notification Type 1 elements for a
+ * {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class SmsCbCmasInfo implements Parcelable {
+
+    // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+    /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+    public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+    /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+    /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+    /** Child abduction emergency (AMBER Alert). */
+    public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+    /** CMAS test message. */
+    public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+    /** CMAS exercise. */
+    public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+    /** CMAS category for operator defined use. */
+    public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+    /** CMAS category for warning types that are reserved for future extension. */
+    public static final int CMAS_CLASS_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CLASS_"},
+            value = {
+                    CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT,
+                    CMAS_CLASS_EXTREME_THREAT,
+                    CMAS_CLASS_SEVERE_THREAT,
+                    CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY,
+                    CMAS_CLASS_REQUIRED_MONTHLY_TEST,
+                    CMAS_CLASS_CMAS_EXERCISE,
+                    CMAS_CLASS_OPERATOR_DEFINED_USE,
+                    CMAS_CLASS_UNKNOWN,
+            })
+    public @interface Class {}
+
+    // CMAS alert category (in CDMA type 1 elements record).
+
+    /** CMAS alert category: Geophysical including landslide. */
+    public static final int CMAS_CATEGORY_GEO = 0x00;
+
+    /** CMAS alert category: Meteorological including flood. */
+    public static final int CMAS_CATEGORY_MET = 0x01;
+
+    /** CMAS alert category: General emergency and public safety. */
+    public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+    /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+    public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+    /** CMAS alert category: Rescue and recovery. */
+    public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+    /** CMAS alert category: Fire suppression and rescue. */
+    public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+    /** CMAS alert category: Medical and public health. */
+    public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+    /** CMAS alert category: Pollution and other environmental. */
+    public static final int CMAS_CATEGORY_ENV = 0x07;
+
+    /** CMAS alert category: Public and private transportation. */
+    public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+    /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+    public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+    /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+    public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+    /** CMAS alert category: Other events. */
+    public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+    /**
+     * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CATEORY_"},
+            value = {
+                    CMAS_CATEGORY_GEO,
+                    CMAS_CATEGORY_MET,
+                    CMAS_CATEGORY_SAFETY,
+                    CMAS_CATEGORY_SECURITY,
+                    CMAS_CATEGORY_RESCUE,
+                    CMAS_CATEGORY_FIRE,
+                    CMAS_CATEGORY_HEALTH,
+                    CMAS_CATEGORY_ENV,
+                    CMAS_CATEGORY_TRANSPORT,
+                    CMAS_CATEGORY_INFRA,
+                    CMAS_CATEGORY_CBRNE,
+                    CMAS_CATEGORY_OTHER,
+                    CMAS_CATEGORY_UNKNOWN,
+            })
+    public @interface Category {}
+
+    // CMAS response type (in CDMA type 1 elements record).
+
+    /** CMAS response type: Take shelter in place. */
+    public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+    /** CMAS response type: Evacuate (Relocate). */
+    public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+    /** CMAS response type: Make preparations. */
+    public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+    /** CMAS response type: Execute a pre-planned activity. */
+    public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+    /** CMAS response type: Attend to information sources. */
+    public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+    /** CMAS response type: Avoid hazard. */
+    public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+    /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+    public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+    /** CMAS response type: No action recommended. */
+    public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+    /**
+     * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_RESPONSE_TYPE_"},
+            value = {
+                    CMAS_RESPONSE_TYPE_SHELTER,
+                    CMAS_RESPONSE_TYPE_EVACUATE,
+                    CMAS_RESPONSE_TYPE_PREPARE,
+                    CMAS_RESPONSE_TYPE_EXECUTE,
+                    CMAS_RESPONSE_TYPE_MONITOR,
+                    CMAS_RESPONSE_TYPE_AVOID,
+                    CMAS_RESPONSE_TYPE_ASSESS,
+                    CMAS_RESPONSE_TYPE_NONE,
+                    CMAS_RESPONSE_TYPE_UNKNOWN,
+    })
+    public @interface ResponseType {}
+
+    // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS severity type: Extraordinary threat to life or property. */
+    public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+    /** CMAS severity type: Significant threat to life or property. */
+    public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+    /**
+     * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_SEVERITY_"},
+            value = {
+                    CMAS_SEVERITY_EXTREME,
+                    CMAS_SEVERITY_SEVERE,
+                    CMAS_SEVERITY_UNKNOWN,
+            })
+    public @interface Severity {}
+
+    // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS urgency type: Responsive action should be taken immediately. */
+    public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+    /** CMAS urgency type: Responsive action should be taken within the next hour. */
+    public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+    /**
+     * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_URGENCY_"},
+            value = {
+                    CMAS_URGENCY_IMMEDIATE,
+                    CMAS_URGENCY_EXPECTED,
+                    CMAS_URGENCY_UNKNOWN,
+            })
+    public @interface Urgency {}
+
+    // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+    public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+    /** CMAS certainty type: Likely (probability > ~50%). */
+    public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+    /**
+     * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CERTAINTY_"},
+            value = {
+                    CMAS_CERTAINTY_OBSERVED,
+                    CMAS_CERTAINTY_LIKELY,
+                    CMAS_CERTAINTY_UNKNOWN,
+            })
+    public @interface Certainty {}
+
+    /** CMAS message class. */
+    private final @Class int mMessageClass;
+
+    /** CMAS category. */
+    private final @Category int mCategory;
+
+    /** CMAS response type. */
+    private final @ResponseType int mResponseType;
+
+    /** CMAS severity. */
+    private final @Severity int mSeverity;
+
+    /** CMAS urgency. */
+    private final @Urgency int mUrgency;
+
+    /** CMAS certainty. */
+    private final @Certainty int mCertainty;
+
+    /** Create a new SmsCbCmasInfo object with the specified values. */
+    public SmsCbCmasInfo(@Class int messageClass, @Category int category,
+            @ResponseType int responseType,
+            @Severity int severity, @Urgency int urgency, @Certainty int certainty) {
+        mMessageClass = messageClass;
+        mCategory = category;
+        mResponseType = responseType;
+        mSeverity = severity;
+        mUrgency = urgency;
+        mCertainty = certainty;
+    }
+
+    /** Create a new SmsCbCmasInfo object from a Parcel. */
+    SmsCbCmasInfo(Parcel in) {
+        mMessageClass = in.readInt();
+        mCategory = in.readInt();
+        mResponseType = in.readInt();
+        mSeverity = in.readInt();
+        mUrgency = in.readInt();
+        mCertainty = in.readInt();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageClass);
+        dest.writeInt(mCategory);
+        dest.writeInt(mResponseType);
+        dest.writeInt(mSeverity);
+        dest.writeInt(mUrgency);
+        dest.writeInt(mCertainty);
+    }
+
+    /**
+     * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+     * @return one of the {@code CMAS_CLASS} values
+     */
+    public @Class int getMessageClass() {
+        return mMessageClass;
+    }
+
+    /**
+     * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+     * @return one of the {@code CMAS_CATEGORY} values
+     */
+    public @Category int getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+     * @return one of the {@code CMAS_RESPONSE_TYPE} values
+     */
+    public @ResponseType int getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+     * @return one of the {@code CMAS_SEVERITY} values
+     */
+    public @Severity int getSeverity() {
+        return mSeverity;
+    }
+
+    /**
+     * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+     * @return one of the {@code CMAS_URGENCY} values
+     */
+    public @Urgency int getUrgency() {
+        return mUrgency;
+    }
+
+    /**
+     * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+     *
+     * @return one of the {@code CMAS_CERTAINTY} values
+     */
+    public @Certainty int getCertainty() {
+        return mCertainty;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+                + ", responseType=" + mResponseType + ", severity=" + mSeverity
+                + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     *
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    @NonNull
+    public static final Parcelable.Creator<SmsCbCmasInfo> CREATOR =
+            new Parcelable.Creator<SmsCbCmasInfo>() {
+        @Override
+        public SmsCbCmasInfo createFromParcel(Parcel in) {
+            return new SmsCbCmasInfo(in);
+        }
+
+        @Override
+        public SmsCbCmasInfo[] newArray(int size) {
+            return new SmsCbCmasInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..2a7f7ad
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Arrays;
+
+/**
+ * Contains information elements for a GSM or UMTS ETWS (Earthquake and Tsunami Warning
+ * System) warning notification. Supported values for each element are defined in 3GPP TS 23.041.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class SmsCbEtwsInfo implements Parcelable {
+
+    /** ETWS warning type for earthquake. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+    /** ETWS warning type for tsunami. */
+    public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+    /** ETWS warning type for earthquake and tsunami. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+    /** ETWS warning type for test messages. */
+    public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
+
+    /** ETWS warning type for other emergency types. */
+    public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
+
+    /** Unknown ETWS warning type. */
+    public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ETWS_WARNING_TYPE_"},
+            value = {
+                    ETWS_WARNING_TYPE_EARTHQUAKE,
+                    ETWS_WARNING_TYPE_TSUNAMI,
+                    ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI,
+                    ETWS_WARNING_TYPE_TEST_MESSAGE,
+                    ETWS_WARNING_TYPE_OTHER_EMERGENCY,
+                    ETWS_WARNING_TYPE_UNKNOWN,
+            })
+    public @interface WarningType {}
+
+    /** One of the ETWS warning type constants defined in this class. */
+    private final @WarningType int mWarningType;
+
+    /** Whether or not to activate the emergency user alert tone and vibration. */
+    private final boolean mIsEmergencyUserAlert;
+
+    /** Whether or not to activate a popup alert. */
+    private final boolean mIsPopupAlert;
+
+    /** Whether ETWS primary message or not/ */
+    private final boolean mIsPrimary;
+
+    /**
+     * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
+     * and digital signature if received. Therefore it is treated as a raw byte array and
+     * parceled with the broadcast intent if present, but the timestamp is only computed if an
+     * application asks for the individual components.
+     */
+    @Nullable
+    private final byte[] mWarningSecurityInformation;
+
+    /**
+     * Create a new SmsCbEtwsInfo object with the specified values.
+     * @param warningType the type of ETWS warning
+     * @param isEmergencyUserAlert whether the warning is an emergency alert, which will activate
+     *                             the user alert tone and vibration
+     * @param isPopupAlert whether the warning will activate a popup alert
+     * @param isPrimary whether this is an ETWS primary message
+     * @param warningSecurityInformation 50-byte security information (for primary notifications
+     *                                   on GSM only).
+     */
+    public SmsCbEtwsInfo(@WarningType int warningType, boolean isEmergencyUserAlert,
+            boolean isPopupAlert,
+            boolean isPrimary, @Nullable byte[] warningSecurityInformation) {
+        mWarningType = warningType;
+        mIsEmergencyUserAlert = isEmergencyUserAlert;
+        mIsPopupAlert = isPopupAlert;
+        mIsPrimary = isPrimary;
+        mWarningSecurityInformation = warningSecurityInformation;
+    }
+
+    /** Create a new SmsCbEtwsInfo object from a Parcel. */
+    SmsCbEtwsInfo(Parcel in) {
+        mWarningType = in.readInt();
+        mIsEmergencyUserAlert = (in.readInt() != 0);
+        mIsPopupAlert = (in.readInt() != 0);
+        mIsPrimary = (in.readInt() != 0);
+        mWarningSecurityInformation = in.createByteArray();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mWarningType);
+        dest.writeInt(mIsEmergencyUserAlert ? 1 : 0);
+        dest.writeInt(mIsPopupAlert ? 1 : 0);
+        dest.writeInt(mIsPrimary ? 1 : 0);
+        dest.writeByteArray(mWarningSecurityInformation);
+    }
+
+    /**
+     * Returns the ETWS warning type.
+     * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
+     */
+    public @WarningType int getWarningType() {
+        return mWarningType;
+    }
+
+    /**
+     * Returns the ETWS emergency user alert flag. If the ETWS message is an emergency alert, it
+     * will activate an alert tone and vibration.
+     * @return true to notify terminal to activate emergency user alert; false otherwise
+     */
+    public boolean isEmergencyUserAlert() {
+        return mIsEmergencyUserAlert;
+    }
+
+    /**
+     * Returns the ETWS activate popup flag.
+     * @return true to notify terminal to activate display popup; false otherwise
+     */
+    public boolean isPopupAlert() {
+        return mIsPopupAlert;
+    }
+
+    /**
+     * Returns the ETWS format flag.
+     * @return true if the message is primary message, otherwise secondary message
+     */
+    public boolean isPrimary() {
+        return mIsPrimary;
+    }
+
+    /**
+     * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
+     * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+     */
+    public long getPrimaryNotificationTimestamp() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
+            return 0;
+        }
+
+        int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
+        int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
+        int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
+        int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
+        int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
+        int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
+
+        // For the timezone, the most significant bit of the
+        // least significant nibble is the sign byte
+        // (meaning the max range of this field is 79 quarter-hours,
+        // which is more than enough)
+
+        byte tzByte = mWarningSecurityInformation[6];
+
+        // Mask out sign bit.
+        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+        // timezoneOffset is in quarter hours.
+        int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
+
+        LocalDateTime localDateTime = LocalDateTime.of(
+                // We only need to support years above 2000.
+                year + 2000,
+                month /* 1-12 */,
+                day,
+                hour,
+                minute,
+                second);
+
+        long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
+        // Convert to milliseconds, ignore overflow.
+        return epochSeconds * 1000;
+    }
+
+    /**
+     * Returns the digital signature (GSM primary notifications only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a byte array containing a copy of the primary notification digital signature
+     */
+    @Nullable
+    public byte[] getPrimaryNotificationSignature() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
+            return null;
+        }
+        return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
+                + mIsEmergencyUserAlert + ", activatePopup=" + mIsPopupAlert + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    @NonNull
+    public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
+        @Override
+        public SmsCbEtwsInfo createFromParcel(Parcel in) {
+            return new SmsCbEtwsInfo(in);
+        }
+
+        @Override
+        public SmsCbEtwsInfo[] newArray(int size) {
+            return new SmsCbEtwsInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..adf7154
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbLocation.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the location and geographical scope of a cell broadcast message.
+ * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
+ * geographical scope is cell wide or Location Area wide. For CDMA, the
+ * broadcast geographical scope is always PLMN wide.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmsCbLocation implements Parcelable {
+
+    /** The PLMN. Note that this field may be an empty string. */
+    @NonNull
+    private final String mPlmn;
+
+    private final int mLac;
+    private final int mCid;
+
+    /**
+     * Construct an empty location object. This is used for some test cases, and for
+     * cell broadcasts saved in older versions of the database without location info.
+     * @hide
+     */
+    public SmsCbLocation() {
+        mPlmn = "";
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     * @hide
+     */
+    public SmsCbLocation(String plmn) {
+        mPlmn = plmn;
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     * @hide
+     */
+    public SmsCbLocation(String plmn, int lac, int cid) {
+        mPlmn = plmn;
+        mLac = lac;
+        mCid = cid;
+    }
+
+    /**
+     * Initialize the object from a Parcel.
+     * @hide
+     */
+    public SmsCbLocation(Parcel in) {
+        mPlmn = in.readString();
+        mLac = in.readInt();
+        mCid = in.readInt();
+    }
+
+    /**
+     * Returns the MCC/MNC of the network as a String.
+     * @return the PLMN identifier (MCC+MNC) as a String
+     */
+    @NonNull
+    public String getPlmn() {
+        return mPlmn;
+    }
+
+    /**
+     * Returns the GSM location area code, or UMTS service area code.
+     * @return location area code, -1 if unknown, 0xffff max legal value
+     */
+    public int getLac() {
+        return mLac;
+    }
+
+    /**
+     * Returns the GSM or UMTS cell ID.
+     * @return gsm cell id, -1 if unknown, 0xffff max legal value
+     */
+    public int getCid() {
+        return mCid;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mPlmn.hashCode();
+        hash = hash * 31 + mLac;
+        hash = hash * 31 + mCid;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || !(o instanceof SmsCbLocation)) {
+            return false;
+        }
+        SmsCbLocation other = (SmsCbLocation) o;
+        return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
+    }
+
+    @Override
+    public String toString() {
+        return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
+    }
+
+    /**
+     * Test whether this location is within the location area of the specified object.
+     *
+     * @param area the location area to compare with this location
+     * @return true if this location is contained within the specified location area
+     */
+    public boolean isInLocationArea(@NonNull SmsCbLocation area) {
+        if (mCid != -1 && mCid != area.mCid) {
+            return false;
+        }
+        if (mLac != -1 && mLac != area.mLac) {
+            return false;
+        }
+        return mPlmn.equals(area.mPlmn);
+    }
+
+    /**
+     * Test whether this location is within the location area of the CellLocation.
+     *
+     * @param plmn the PLMN to use for comparison
+     * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
+     * @param cid the Cell ID to compare with
+     * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
+     */
+    public boolean isInLocationArea(@Nullable String plmn, int lac, int cid) {
+        if (!mPlmn.equals(plmn)) {
+            return false;
+        }
+
+        if (mLac != -1 && mLac != lac) {
+            return false;
+        }
+
+        if (mCid != -1 && mCid != cid) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPlmn);
+        dest.writeInt(mLac);
+        dest.writeInt(mCid);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SmsCbLocation> CREATOR =
+            new Parcelable.Creator<SmsCbLocation>() {
+        @Override
+        public SmsCbLocation createFromParcel(Parcel in) {
+            return new SmsCbLocation(in);
+        }
+
+        @Override
+        public SmsCbLocation[] newArray(int size) {
+            return new SmsCbLocation[size];
+        }
+    };
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..c078764
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.CellBroadcasts;
+
+import com.android.internal.telephony.CbGeoUtils;
+import com.android.internal.telephony.CbGeoUtils.Geometry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
+ *
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ *  roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmsCbMessage implements Parcelable {
+
+    /** @hide */
+    public static final String LOG_TAG = "SMSCB";
+
+    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+    /** Location / service area wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2;
+
+    /** Cell wide geographical scope (GSM/UMTS only). */
+    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "GEOGRAPHICAL_SCOPE_" }, value = {
+            GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE,
+            GEOGRAPHICAL_SCOPE_PLMN_WIDE,
+            GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE,
+            GEOGRAPHICAL_SCOPE_CELL_WIDE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GeographicalScope {}
+
+    /** GSM or UMTS format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP = 1;
+
+    /** CDMA format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_FORMAT_" }, value = {
+            MESSAGE_FORMAT_3GPP,
+            MESSAGE_FORMAT_3GPP2
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessageFormat {}
+
+    /** Normal message priority. */
+    public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+    /** Interactive message priority. */
+    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+    /** Urgent message priority. */
+    public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+    /** Emergency message priority. */
+    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_PRIORITY_" }, value = {
+            MESSAGE_PRIORITY_NORMAL,
+            MESSAGE_PRIORITY_INTERACTIVE,
+            MESSAGE_PRIORITY_URGENT,
+            MESSAGE_PRIORITY_EMERGENCY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessagePriority {}
+
+    /**
+     * ATIS-0700041 Section 5.2.8 WAC Geo-Fencing Maximum Wait Time Table 12.
+     * @hide
+     */
+    public static final int MAXIMUM_WAIT_TIME_NOT_SET = 255;
+
+    /** Format of this message (for interpretation of service category values). */
+    private final int mMessageFormat;
+
+    /** Geographical scope of broadcast. */
+    private final int mGeographicalScope;
+
+    /**
+     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+     * a cell broadcast for duplicate detection.
+     */
+    private final int mSerialNumber;
+
+    /**
+     * Location identifier for this message. It consists of the current operator MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included for comparison. If the GS is
+     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+     */
+    @NonNull
+    private final SmsCbLocation mLocation;
+
+    /**
+     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+     * or {@link #getCmasWarningInfo()}.
+     */
+    private final int mServiceCategory;
+
+    /** Message language, as a two-character string, e.g. "en". */
+    @Nullable
+    private final String mLanguage;
+
+    /** Message body, as a String. */
+    @Nullable
+    private final String mBody;
+
+    /** Message priority (including emergency priority). */
+    private final int mPriority;
+
+    /** ETWS warning notification information (ETWS warnings only). */
+    @Nullable
+    private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+    /** CMAS warning notification information (CMAS warnings only). */
+    @Nullable
+    private final SmsCbCmasInfo mCmasWarningInfo;
+
+    /**
+     * Geo-Fencing Maximum Wait Time in second, a device shall allow to determine its position
+     * meeting operator policy. If the device is unable to determine its position meeting operator
+     * policy within the GeoFencing Maximum Wait Time, it shall present the alert to the user and
+     * discontinue further positioning determination for the alert.
+     */
+    private final int mMaximumWaitTimeSec;
+
+    /** UNIX timestamp of when the message was received. */
+    private final long mReceivedTimeMillis;
+
+    /** CMAS warning area coordinates. */
+    private final List<Geometry> mGeometries;
+
+    /**
+     * Create a new SmsCbMessage with the specified data.
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
+            @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
+            @Nullable SmsCbCmasInfo cmasWarningInfo) {
+
+        this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
+                body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */,
+                null /* geometries */, System.currentTimeMillis());
+    }
+
+    /**
+     * Create a new {@link SmsCbMessage} with the warning area coordinates information.
+     * @hide
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            SmsCbLocation location, int serviceCategory, String language, String body,
+            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
+            int maximumWaitTimeSec, List<Geometry> geometries, long receivedTimeMillis) {
+        mMessageFormat = messageFormat;
+        mGeographicalScope = geographicalScope;
+        mSerialNumber = serialNumber;
+        mLocation = location;
+        mServiceCategory = serviceCategory;
+        mLanguage = language;
+        mBody = body;
+        mPriority = priority;
+        mEtwsWarningInfo = etwsWarningInfo;
+        mCmasWarningInfo = cmasWarningInfo;
+        mReceivedTimeMillis = receivedTimeMillis;
+        mGeometries = geometries;
+        mMaximumWaitTimeSec = maximumWaitTimeSec;
+    }
+
+    /**
+     * Create a new SmsCbMessage object from a Parcel.
+     * @hide
+     */
+    public SmsCbMessage(@NonNull Parcel in) {
+        mMessageFormat = in.readInt();
+        mGeographicalScope = in.readInt();
+        mSerialNumber = in.readInt();
+        mLocation = new SmsCbLocation(in);
+        mServiceCategory = in.readInt();
+        mLanguage = in.readString();
+        mBody = in.readString();
+        mPriority = in.readInt();
+        int type = in.readInt();
+        switch (type) {
+            case 'E':
+                // unparcel ETWS warning information
+                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+                mCmasWarningInfo = null;
+                break;
+
+            case 'C':
+                // unparcel CMAS warning information
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = new SmsCbCmasInfo(in);
+                break;
+
+            default:
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = null;
+        }
+        mReceivedTimeMillis = in.readLong();
+        String geoStr = in.readString();
+        mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+        mMaximumWaitTimeSec = in.readInt();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageFormat);
+        dest.writeInt(mGeographicalScope);
+        dest.writeInt(mSerialNumber);
+        mLocation.writeToParcel(dest, flags);
+        dest.writeInt(mServiceCategory);
+        dest.writeString(mLanguage);
+        dest.writeString(mBody);
+        dest.writeInt(mPriority);
+        if (mEtwsWarningInfo != null) {
+            // parcel ETWS warning information
+            dest.writeInt('E');
+            mEtwsWarningInfo.writeToParcel(dest, flags);
+        } else if (mCmasWarningInfo != null) {
+            // parcel CMAS warning information
+            dest.writeInt('C');
+            mCmasWarningInfo.writeToParcel(dest, flags);
+        } else {
+            // no ETWS or CMAS warning information
+            dest.writeInt('0');
+        }
+        dest.writeLong(mReceivedTimeMillis);
+        dest.writeString(
+                mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
+        dest.writeInt(mMaximumWaitTimeSec);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SmsCbMessage> CREATOR =
+            new Parcelable.Creator<SmsCbMessage>() {
+        @Override
+        public SmsCbMessage createFromParcel(Parcel in) {
+            return new SmsCbMessage(in);
+        }
+
+        @Override
+        public SmsCbMessage[] newArray(int size) {
+            return new SmsCbMessage[size];
+        }
+    };
+
+    /**
+     * Return the geographical scope of this message (GSM/UMTS only).
+     *
+     * @return Geographical scope
+     */
+    public @GeographicalScope int getGeographicalScope() {
+        return mGeographicalScope;
+    }
+
+    /**
+     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+     * the location code uniquely identify a cell broadcast for duplicate detection.
+     *
+     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+     */
+    public int getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    /**
+     * Return the location identifier for this message, consisting of the MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+     * if the location is included within another location area or within a PLMN and CellLocation.
+     *
+     * @return the geographical location code for duplicate message detection
+     */
+    @NonNull
+    public android.telephony.SmsCbLocation getLocation() {
+        return mLocation;
+    }
+
+    /**
+     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+     * provided by the category is available via {@link #getEtwsWarningInfo()} or
+     * {@link #getCmasWarningInfo()} in a radio technology independent format.
+     *
+     * @return the radio technology specific service category
+     */
+    public int getServiceCategory() {
+        return mServiceCategory;
+    }
+
+    /**
+     * Get the ISO-639-1 language code for this message, or null if unspecified
+     *
+     * @return Language code
+     */
+    @Nullable
+    public String getLanguageCode() {
+        return mLanguage;
+    }
+
+    /**
+     * Get the body of this message, or null if no body available
+     *
+     * @return Body, or null
+     */
+    @Nullable
+    public String getMessageBody() {
+        return mBody;
+    }
+
+    /**
+     * Get the warning area coordinates information represent by polygons and circles.
+     * @return a list of geometries, {@link Nullable} means there is no coordinate information
+     * associated to this message.
+     * @hide
+     */
+    @Nullable
+    public List<Geometry> getGeometries() {
+        return mGeometries;
+    }
+
+    /**
+     * Get the Geo-Fencing Maximum Wait Time.
+     * @return the time in second.
+     * @hide
+     */
+    public int getMaximumWaitingTime() {
+        return mMaximumWaitTimeSec;
+    }
+
+    /**
+     * Get the time when this message was received.
+     * @return the time in millisecond
+     */
+    public long getReceivedTime() {
+        return mReceivedTimeMillis;
+    }
+
+    /**
+     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+     * @return an integer representing 3GPP or 3GPP2 message format
+     */
+    public @MessageFormat int getMessageFormat() {
+        return mMessageFormat;
+    }
+
+    /**
+     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+     * @return an integer representing the message priority
+     */
+    public @MessagePriority int getMessagePriority() {
+        return mPriority;
+    }
+
+    /**
+     * If this is an ETWS warning notification then this method will return an object containing
+     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+     * ETWS primary notification timestamp and digital signature if received.
+     *
+     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
+     */
+    @Nullable
+    public SmsCbEtwsInfo getEtwsWarningInfo() {
+        return mEtwsWarningInfo;
+    }
+
+    /**
+     * If this is a CMAS warning notification then this method will return an object containing
+     * the CMAS message class, category, response type, severity, urgency and certainty.
+     * The message class is always present. Severity, urgency and certainty are present for CDMA
+     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+     * except for the Presidential-level alert category. Category and response type are only
+     * available for CDMA notifications containing a type 1 elements record.
+     *
+     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
+     */
+    @Nullable
+    public SmsCbCmasInfo getCmasWarningInfo() {
+        return mCmasWarningInfo;
+    }
+
+    /**
+     * Return whether this message is an emergency (PWS) message type.
+     * @return true if the message is an emergency notification; false otherwise
+     */
+    public boolean isEmergencyMessage() {
+        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+    }
+
+    /**
+     * Return whether this message is an ETWS warning alert.
+     * @return true if the message is an ETWS warning notification; false otherwise
+     */
+    public boolean isEtwsMessage() {
+        return mEtwsWarningInfo != null;
+    }
+
+    /**
+     * Return whether this message is a CMAS warning alert.
+     * @return true if the message is a CMAS warning notification; false otherwise
+     */
+    public boolean isCmasMessage() {
+        return mCmasWarningInfo != null;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+                + ", priority=" + mPriority
+                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
+                + ", maximumWaitingTime = " + mMaximumWaitTimeSec
+                + ", geo=" + (mGeometries != null
+                ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
+                + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return the {@link ContentValues} instance that includes the cell broadcast data.
+     */
+    @NonNull
+    public ContentValues getContentValues() {
+        ContentValues cv = new ContentValues(16);
+        cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
+        if (mLocation.getPlmn() != null) {
+            cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
+        }
+        if (mLocation.getLac() != -1) {
+            cv.put(CellBroadcasts.LAC, mLocation.getLac());
+        }
+        if (mLocation.getCid() != -1) {
+            cv.put(CellBroadcasts.CID, mLocation.getCid());
+        }
+        cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber());
+        cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory());
+        cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode());
+        cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody());
+        cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat());
+        cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority());
+
+        SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
+        if (etwsInfo != null) {
+            cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
+        }
+
+        SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
+        if (cmasInfo != null) {
+            cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
+            cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
+            cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
+            cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
+            cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
+            cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
+        }
+
+        cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis);
+
+        if (mGeometries != null) {
+            cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries));
+        } else {
+            cv.put(CellBroadcasts.GEOMETRIES, (String) null);
+        }
+
+        cv.put(CellBroadcasts.MAXIMUM_WAIT_TIME, mMaximumWaitTimeSec);
+
+        return cv;
+    }
+
+    /**
+     * Create a {@link SmsCbMessage} instance from a row in the cell broadcast database.
+     * @param cursor an open SQLite cursor pointing to the row to read
+     * @return a {@link SmsCbMessage} instance.
+     * @throws IllegalArgumentException if one of the required columns is missing
+     */
+    @NonNull
+    public static SmsCbMessage createFromCursor(@NonNull Cursor cursor) {
+        int geoScope = cursor.getInt(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
+        int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
+        int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY));
+        String language = cursor.getString(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE));
+        String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY));
+        int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
+        int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
+
+        String plmn;
+        int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
+        if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
+            plmn = cursor.getString(plmnColumn);
+        } else {
+            plmn = null;
+        }
+
+        int lac;
+        int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC);
+        if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
+            lac = cursor.getInt(lacColumn);
+        } else {
+            lac = -1;
+        }
+
+        int cid;
+        int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID);
+        if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
+            cid = cursor.getInt(cidColumn);
+        } else {
+            cid = -1;
+        }
+
+        SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
+
+        SmsCbEtwsInfo etwsInfo;
+        int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
+        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
+            int warningType = cursor.getInt(etwsWarningTypeColumn);
+            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
+        } else {
+            etwsInfo = null;
+        }
+
+        SmsCbCmasInfo cmasInfo = null;
+        int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS);
+        if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
+            int messageClass = cursor.getInt(cmasMessageClassColumn);
+
+            int cmasCategory;
+            int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY);
+            if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
+                cmasCategory = cursor.getInt(cmasCategoryColumn);
+            } else {
+                cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+            }
+
+            int responseType;
+            int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE);
+            if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
+                responseType = cursor.getInt(cmasResponseTypeColumn);
+            } else {
+                responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+            }
+
+            int severity;
+            int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY);
+            if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
+                severity = cursor.getInt(cmasSeverityColumn);
+            } else {
+                severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+            }
+
+            int urgency;
+            int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY);
+            if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
+                urgency = cursor.getInt(cmasUrgencyColumn);
+            } else {
+                urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+            }
+
+            int certainty;
+            int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY);
+            if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
+                certainty = cursor.getInt(cmasCertaintyColumn);
+            } else {
+                certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+            }
+
+            cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
+                    urgency, certainty);
+        }
+
+        String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES));
+        List<Geometry> geometries =
+                geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+
+        long receivedTimeMillis = cursor.getLong(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
+
+        int maximumWaitTimeSec = cursor.getInt(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME));
+
+        return new SmsCbMessage(format, geoScope, serialNum, location, category,
+                language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries,
+                receivedTimeMillis);
+    }
+
+    /**
+     * @return {@code True} if this message needs geo-fencing check.
+     */
+    public boolean needGeoFencingCheck() {
+        return mMaximumWaitTimeSec > 0 && mGeometries != null;
+    }
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index cd0f57e..8c14cb4 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1670,15 +1670,14 @@
      *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage]
      * @return true if successful, false otherwise
      * @see #disableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean enableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
@@ -1717,16 +1716,15 @@
      *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage}
      * @return true if successful, false otherwise
      *
      * @see #enableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean disableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
@@ -1746,8 +1744,8 @@
 
     /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
-     * message identifier range and RAN type. The RAN type specify this message ID
-     * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+     * message identifier range and RAN type. The RAN type specifies if this message ID
+     * belongs to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
      * the same message identifier, they must both disable it for the device to stop
      * receiving those messages. All received messages will be broadcast in an
      * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
@@ -1759,26 +1757,29 @@
      * dialog. If this method is called on a device that has multiple active subscriptions, this
      * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
      * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, which will result in the operation
+     * being completed on the subscription associated with logical slot 0. Use
+     * {@link #getSmsManagerForSubscriptionId(int)} to ensure the operation is performed on the
+     * correct subscription.
      * </p>
      *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      * @see #disableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -1818,22 +1819,24 @@
      * operation is performed on the correct subscription.
      * </p>
      *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      *
      * @see #enableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index bb2269f..f527bc3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -578,9 +578,11 @@
         PackageManager packageManager = context.getPackageManager();
         PackageInfo packageInfo;
         try {
-            packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            packageInfo = packageManager.getPackageInfo(packageName,
+                PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
-            throw new IllegalArgumentException("Unknown package: " + packageName, e);
+            Log.d("SubscriptionInfo", "canManageSubscription: Unknown package: " + packageName, e);
+            return false;
         }
         for (UiccAccessRule rule : allAccessRules) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
@@ -612,7 +614,9 @@
      */
     public @Nullable List<UiccAccessRule> getAllAccessRules() {
         List<UiccAccessRule> merged = new ArrayList<>();
-        if (mNativeAccessRules != null) merged.addAll(getAccessRules());
+        if (mNativeAccessRules != null) {
+            merged.addAll(getAccessRules());
+        }
         if (mCarrierConfigAccessRules != null) {
             merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
         }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5e47e49..dcd35fd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,6 +53,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.TelephonyManager.NetworkType;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
 import android.util.DisplayMetrics;
@@ -895,6 +896,11 @@
      */
     public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
 
+    /**
+     * Integer extra to specify SIM slot index.
+     */
+    public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
+
     private final Context mContext;
     private volatile INetworkPolicyManager mNetworkPolicy;
 
@@ -2122,6 +2128,7 @@
         if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
         intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
     }
 
@@ -2290,14 +2297,19 @@
     }
 
     /**
-     * Returns the resources associated with Subscription.
+     * Returns the {@link Resources} from the given {@link Context} for the MCC/MNC associated with
+     * the subscription. If the subscription ID is invalid, the base resources are returned instead.
+     *
+     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
      * @param context Context object
-     * @param subId Subscription Id of Subscription who's resources are required
+     * @param subId Subscription Id of Subscription whose resources are required
      * @return Resources associated with Subscription.
      * @hide
      */
-    @UnsupportedAppUsage
-    public static Resources getResourcesForSubId(Context context, int subId) {
+    @NonNull
+    @SystemApi
+    public static Resources getResourcesForSubId(@NonNull Context context, int subId) {
         return getResourcesForSubId(context, subId, false);
     }
 
@@ -2454,10 +2466,51 @@
      */
     public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
             @DurationMillisLong long timeoutMillis) {
+        setSubscriptionOverrideUnmetered(subId, null, overrideUnmetered, timeoutMillis);
+    }
+
+    /**
+     * Temporarily override the billing relationship between a carrier and
+     * a specific subscriber to be considered unmetered for the given network
+     * types. This will be reflected to apps via
+     * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
+     * This method is only accessible to the following narrow set of apps:
+     * <ul>
+     * <li>The carrier app for this subscriberId, as determined by
+     * {@link TelephonyManager#hasCarrierPrivileges()}.
+     * <li>The carrier app explicitly delegated access through
+     * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+     * </ul>
+     *
+     * @param subId the subscriber this override applies to.
+     * @param networkTypes all network types to set an override for. A null
+     *            network type means to apply the override to all network types.
+     *            Any unspecified network types will default to metered.
+     * @param overrideUnmetered set if the billing relationship should be
+     *            considered unmetered.
+     * @param timeoutMillis the timeout after which the requested override will
+     *            be automatically cleared, or {@code 0} to leave in the
+     *            requested state until explicitly cleared, or the next reboot,
+     *            whichever happens first.
+     * @throws SecurityException if the caller doesn't meet the requirements
+     *            outlined above.
+     * {@hide}
+     */
+    public void setSubscriptionOverrideUnmetered(int subId,
+            @Nullable @NetworkType int[] networkTypes, boolean overrideUnmetered,
+            @DurationMillisLong long timeoutMillis) {
         try {
+            long networkTypeMask = 0;
+            if (networkTypes != null) {
+                for (int networkType : networkTypes) {
+                    networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+                }
+            } else {
+                networkTypeMask = ~0;
+            }
             final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
             getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
-                    timeoutMillis, mContext.getOpPackageName());
+                    networkTypeMask, timeoutMillis, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2489,10 +2542,52 @@
      */
     public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
             @DurationMillisLong long timeoutMillis) {
+        setSubscriptionOverrideCongested(subId, null, overrideCongested, timeoutMillis);
+    }
+
+    /**
+     * Temporarily override the billing relationship plan between a carrier and
+     * a specific subscriber to be considered congested. This will cause the
+     * device to delay certain network requests when possible, such as developer
+     * jobs that are willing to run in a flexible time window.
+     * <p>
+     * This method is only accessible to the following narrow set of apps:
+     * <ul>
+     * <li>The carrier app for this subscriberId, as determined by
+     * {@link TelephonyManager#hasCarrierPrivileges()}.
+     * <li>The carrier app explicitly delegated access through
+     * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
+     * </ul>
+     *
+     * @param subId the subscriber this override applies to.
+     * @param networkTypes all network types to set an override for. A null
+     *            network type means to apply the override to all network types.
+     *            Any unspecified network types will default to not congested.
+     * @param overrideCongested set if the subscription should be considered
+     *            congested.
+     * @param timeoutMillis the timeout after which the requested override will
+     *            be automatically cleared, or {@code 0} to leave in the
+     *            requested state until explicitly cleared, or the next reboot,
+     *            whichever happens first.
+     * @throws SecurityException if the caller doesn't meet the requirements
+     *             outlined above.
+     * @hide
+     */
+    public void setSubscriptionOverrideCongested(int subId,
+            @Nullable @NetworkType int[] networkTypes, boolean overrideCongested,
+            @DurationMillisLong long timeoutMillis) {
         try {
+            long networkTypeMask = 0;
+            if (networkTypes != null) {
+                for (int networkType : networkTypes) {
+                    networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+                }
+            } else {
+                networkTypeMask = ~0;
+            }
             final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
             getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
-                    timeoutMillis, mContext.getOpPackageName());
+                    networkTypeMask, timeoutMillis, mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2606,15 +2701,17 @@
      * @hide
      */
     public boolean canManageSubscription(SubscriptionInfo info, String packageName) {
-        if (info.getAllAccessRules() == null) {
+        if (info == null || info.getAllAccessRules() == null) {
             return false;
         }
         PackageManager packageManager = mContext.getPackageManager();
         PackageInfo packageInfo;
         try {
-            packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            packageInfo = packageManager.getPackageInfo(packageName,
+                PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
-            throw new IllegalArgumentException("Unknown package: " + packageName, e);
+            logd("Unknown package: " + packageName);
+            return false;
         }
         for (UiccAccessRule rule : info.getAllAccessRules()) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index e0838b3..ec2050f 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -131,7 +131,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (obj instanceof SubscriptionPlan) {
             final SubscriptionPlan other = (SubscriptionPlan) obj;
             return Objects.equals(cycleRule, other.cycleRule)
diff --git a/telephony/java/android/telephony/TelephonyHistogram.java b/telephony/java/android/telephony/TelephonyHistogram.java
index 63bdac5..b94cb60 100644
--- a/telephony/java/android/telephony/TelephonyHistogram.java
+++ b/telephony/java/android/telephony/TelephonyHistogram.java
@@ -15,13 +15,12 @@
  */
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * Parcelable class to store Telephony histogram.
@@ -238,6 +237,8 @@
         }
     }
 
+    @NonNull
+    @Override
     public String toString() {
         String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = "
                 + mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 553bff2..4f276bc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -67,6 +67,7 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
+import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.aidl.IImsRcsFeature;
@@ -270,9 +271,6 @@
     private SubscriptionManager mSubscriptionManager;
     private TelephonyScanManager mTelephonyScanManager;
 
-    private static String multiSimConfig =
-            SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG);
-
     /** Enum indicating multisim variants
      *  DSDS - Dual SIM Dual Standby
      *  DSDA - Dual SIM Dual Active
@@ -364,7 +362,6 @@
         }
     }
 
-
     /**
      * Returns the number of phones available.
      * Returns 0 if none of voice, sms, data is not supported
@@ -397,6 +394,31 @@
         return phoneCount;
     }
 
+    /**
+     *
+     * Return how many phone / logical modem can be active simultaneously, in terms of device
+     * capability.
+     * For example, for a dual-SIM capable device, it always returns 2, even if only one logical
+     * modem / SIM is active (aka in single SIM mode).
+     *
+     * TODO: b/139642279 publicize and rename.
+     * @hide
+     */
+    public static int getMaxPhoneCount() {
+        // TODO: b/139642279 when turning on this feature, remove dependency of
+        // PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on
+        // PROPERTY_MAX_ACTIVE_MODEMS.
+        String rebootRequired = SystemProperties.get(
+                TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
+        if (rebootRequired.equals("false")) {
+            // If no reboot is required, return max possible active modems.
+            return SystemProperties.getInt(
+                    TelephonyProperties.PROPERTY_MAX_ACTIVE_MODEMS, getDefault().getPhoneCount());
+        } else {
+            return getDefault().getPhoneCount();
+        }
+    }
+
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public static TelephonyManager from(Context context) {
@@ -432,8 +454,7 @@
     /** {@hide} */
     @UnsupportedAppUsage
     public boolean isMultiSimEnabled() {
-        return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") ||
-            multiSimConfig.equals("tsts"));
+        return getPhoneCount() > 1;
     }
 
     //
@@ -2428,41 +2449,37 @@
      * @return the lowercase 2 character ISO-3166 country code, or empty string if not available.
      */
     public String getNetworkCountryIso() {
-        return getNetworkCountryIsoForPhone(getPhoneId());
+        return getNetworkCountryIso(getPhoneId());
     }
 
     /**
-     * Returns the ISO country code equivalent of the MCC (Mobile Country Code) of the current
+     * Returns the ISO-3166 country code equivalent of the MCC (Mobile Country Code) of the current
      * registered operator or the cell nearby, if available.
      * <p>
+     * The ISO-3166 country code is provided in lowercase 2 character format.
+     * <p>
+     * Note: In multi-sim, this returns a shared emergency network country iso from other
+     * subscription if the subscription used to create the TelephonyManager doesn't camp on
+     * a network due to some reason (e.g. pin/puk locked), or sim is absent in the corresponding
+     * slot.
      * Note: Result may be unreliable on CDMA networks (use {@link #getPhoneType()} to determine
      * if on a CDMA network).
-     *
-     * @param subId for which Network CountryIso is returned
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public String getNetworkCountryIso(int subId) {
-        return getNetworkCountryIsoForPhone(getPhoneId(subId));
-    }
-
-    /**
-     * Returns the ISO country code equivalent of the current registered
-     * operator's MCC (Mobile Country Code) of a subscription.
      * <p>
-     * Availability: Only when user is registered to a network. Result may be
-     * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if
-     * on a CDMA network).
      *
-     * @param phoneId for which Network CountryIso is returned
+     * @param slotIndex the SIM slot index to get network country ISO.
+     *
+     * @return the lowercase 2 character ISO-3166 country code, or empty string if not available.
+     *
+     * {@hide}
      */
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public String getNetworkCountryIsoForPhone(int phoneId) {
+    @SystemApi
+    @TestApi
+    @NonNull
+    public String getNetworkCountryIso(int slotIndex) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony == null) return "";
-            return telephony.getNetworkCountryIsoForPhone(phoneId);
+            return telephony.getNetworkCountryIsoForPhone(slotIndex);
         } catch (RemoteException ex) {
             return "";
         }
@@ -2829,6 +2846,55 @@
         }
     }
 
+    /**
+     * Returns the bitmask for a given technology (network type)
+     * @param networkType for which bitmask is returned
+     * @return the network type bitmask
+     * {@hide}
+     */
+    public static @NetworkTypeBitMask long getBitMaskForNetworkType(@NetworkType int networkType) {
+        switch(networkType) {
+            case NETWORK_TYPE_GSM:
+                return NETWORK_TYPE_BITMASK_GSM;
+            case NETWORK_TYPE_GPRS:
+                return NETWORK_TYPE_BITMASK_GPRS;
+            case NETWORK_TYPE_EDGE:
+                return NETWORK_TYPE_BITMASK_EDGE;
+            case NETWORK_TYPE_CDMA:
+                return NETWORK_TYPE_BITMASK_CDMA;
+            case NETWORK_TYPE_1xRTT:
+                return NETWORK_TYPE_BITMASK_1xRTT;
+            case NETWORK_TYPE_EVDO_0:
+                return NETWORK_TYPE_BITMASK_EVDO_0;
+            case NETWORK_TYPE_EVDO_A:
+                return NETWORK_TYPE_BITMASK_EVDO_A;
+            case NETWORK_TYPE_EVDO_B:
+                return NETWORK_TYPE_BITMASK_EVDO_B;
+            case NETWORK_TYPE_EHRPD:
+                return NETWORK_TYPE_BITMASK_EHRPD;
+            case NETWORK_TYPE_HSUPA:
+                return NETWORK_TYPE_BITMASK_HSUPA;
+            case NETWORK_TYPE_HSDPA:
+                return NETWORK_TYPE_BITMASK_HSDPA;
+            case NETWORK_TYPE_HSPA:
+                return NETWORK_TYPE_BITMASK_HSPA;
+            case NETWORK_TYPE_HSPAP:
+                return NETWORK_TYPE_BITMASK_HSPAP;
+            case NETWORK_TYPE_UMTS:
+                return NETWORK_TYPE_BITMASK_UMTS;
+            case NETWORK_TYPE_TD_SCDMA:
+                return NETWORK_TYPE_BITMASK_TD_SCDMA;
+            case NETWORK_TYPE_LTE:
+                return NETWORK_TYPE_BITMASK_LTE;
+            case NETWORK_TYPE_LTE_CA:
+                return NETWORK_TYPE_BITMASK_LTE_CA;
+            case NETWORK_TYPE_NR:
+                return NETWORK_TYPE_BITMASK_NR;
+            default:
+                return NETWORK_TYPE_BITMASK_UNKNOWN;
+        }
+    }
+
     //
     //
     // SIM Card
@@ -3083,19 +3149,62 @@
      */
     @SystemApi
     public int getSimCardState() {
-        int simCardState = getSimState();
-        switch (simCardState) {
+        int simState = getSimState();
+        return getSimCardStateFromSimState(simState);
+    }
+
+    /**
+     * Returns a constant indicating the state of the device SIM card in a physical slot.
+     *
+     * @param physicalSlotIndex physical slot index
+     *
+     * @see #SIM_STATE_UNKNOWN
+     * @see #SIM_STATE_ABSENT
+     * @see #SIM_STATE_CARD_IO_ERROR
+     * @see #SIM_STATE_CARD_RESTRICTED
+     * @see #SIM_STATE_PRESENT
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getSimCardState(int physicalSlotIndex) {
+        int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
+        return getSimCardStateFromSimState(simState);
+    }
+
+    /**
+     * Converts SIM state to SIM card state.
+     * @param simState
+     * @return SIM card state
+     */
+    private int getSimCardStateFromSimState(int simState) {
+        switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
             case SIM_STATE_CARD_IO_ERROR:
             case SIM_STATE_CARD_RESTRICTED:
-                return simCardState;
+                return simState;
             default:
                 return SIM_STATE_PRESENT;
         }
     }
 
     /**
+     * Converts a physical slot index to logical slot index.
+     * @param physicalSlotIndex physical slot index
+     * @return logical slot index
+     */
+    private int getLogicalSlotIndex(int physicalSlotIndex) {
+        UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+            return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
+        }
+
+        return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+    }
+
+    /**
      * Returns a constant indicating the state of the card applications on the default SIM card.
      *
      * @see #SIM_STATE_UNKNOWN
@@ -3110,8 +3219,41 @@
      */
     @SystemApi
     public int getSimApplicationState() {
-        int simApplicationState = getSimStateIncludingLoaded();
-        switch (simApplicationState) {
+        int simState = getSimStateIncludingLoaded();
+        return getSimApplicationStateFromSimState(simState);
+    }
+
+    /**
+     * Returns a constant indicating the state of the card applications on the device SIM card in
+     * a physical slot.
+     *
+     * @param physicalSlotIndex physical slot index
+     *
+     * @see #SIM_STATE_UNKNOWN
+     * @see #SIM_STATE_PIN_REQUIRED
+     * @see #SIM_STATE_PUK_REQUIRED
+     * @see #SIM_STATE_NETWORK_LOCKED
+     * @see #SIM_STATE_NOT_READY
+     * @see #SIM_STATE_PERM_DISABLED
+     * @see #SIM_STATE_LOADED
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getSimApplicationState(int physicalSlotIndex) {
+        int simState =
+                SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
+        return getSimApplicationStateFromSimState(simState);
+    }
+
+    /**
+     * Converts SIM state to SIM application state.
+     * @param simState
+     * @return SIM application state
+     */
+    private int getSimApplicationStateFromSimState(int simState) {
+        switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
             case SIM_STATE_CARD_IO_ERROR:
@@ -3122,14 +3264,39 @@
                 // NOT_READY to either LOCKED or LOADED.
                 return SIM_STATE_NOT_READY;
             default:
-                return simApplicationState;
+                return simState;
         }
     }
 
+
     /**
-     * Returns a constant indicating the state of the device SIM card in a slot.
+     * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+     * on the UICC card.
      *
-     * @param slotIndex
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @param appType the uicc app type like {@link APPTYPE_CSIM}
+     * @return true if the specified type of application in UICC CARD or false if no uicc or error.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isApplicationOnUicc(@UiccAppType int appType) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.isApplicationOnUicc(getSubId(), appType);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isApplicationOnUicc", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a constant indicating the state of the device SIM card in a logical slot.
+     *
+     * @param slotIndex logical slot index
      *
      * @see #SIM_STATE_UNKNOWN
      * @see #SIM_STATE_ABSENT
@@ -4391,6 +4558,17 @@
     }
 
     /**
+     * Sim activation type: voice
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_TYPE_VOICE = 0;
+    /**
+     * Sim activation type: data
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_TYPE_DATA = 1;
+
+    /**
      * Initial SIM activation state, unknown. Not set by any carrier apps.
      * @hide
      */
@@ -4917,6 +5095,17 @@
      */
     public static final int DATA_ACTIVITY_DORMANT = 0x00000004;
 
+    /** @hide */
+    @IntDef(prefix = {"DATA_"}, value = {
+        DATA_ACTIVITY_NONE,
+        DATA_ACTIVITY_IN,
+        DATA_ACTIVITY_OUT,
+        DATA_ACTIVITY_INOUT,
+        DATA_ACTIVITY_DORMANT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataActivityType{}
+
     /**
      * Returns a constant indicating the type of activity on a data connection
      * (cellular).
@@ -6475,11 +6664,7 @@
     public int getSimCount() {
         // FIXME Need to get it from Telephony Dev Controller when that gets implemented!
         // and then this method shouldn't be used at all!
-        if(isMultiSimEnabled()) {
-            return getPhoneCount();
-        } else {
-            return 1;
-        }
+        return getPhoneCount();
     }
 
     /**
@@ -8505,7 +8690,12 @@
         return -1;
     }
 
-    /** @hide */
+    /**
+     * @deprecated Use {@link android.telephony.ims.ImsMmTelManager#setVtSettingEnabled(boolean)}
+     * instead.
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void enableVideoCalling(boolean enable) {
@@ -8518,7 +8708,14 @@
         }
     }
 
-    /** @hide */
+    /**
+     * @deprecated Use {@link ImsMmTelManager#isVtSettingEnabled()} instead to check if the user
+     * has enabled the Video Calling setting, {@link ImsMmTelManager#isAvailable(int, int)} to
+     * determine if video calling is available, or {@link ImsMmTelManager#isCapable(int, int)} to
+     * determine if video calling is capable.
+     * @hide
+     */
+    @Deprecated
     @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
@@ -8638,6 +8835,7 @@
      * @param subId Subscription ID
      * @return true if IMS status is registered, false if the IMS status is not registered or a
      * RemoteException occurred.
+     * Use {@link ImsMmTelManager.RegistrationCallback} instead.
      * @hide
      */
     public boolean isImsRegistered(int subId) {
@@ -8674,6 +8872,8 @@
      * used during creation, the default subscription ID will be used.
      * @return true if Voice over LTE is available or false if it is unavailable or unknown.
      * @see SubscriptionManager#getDefaultSubscriptionId()
+     * <p>
+     * Use {@link ImsMmTelManager#isAvailable(int, int)} instead.
      * @hide
      */
     @UnsupportedAppUsage
@@ -8693,6 +8893,7 @@
      * used during creation, the default subscription ID will be used. To query the
      * underlying technology that VT is available on, use {@link #getImsRegTechnologyForMmTel}.
      * @return true if VT is available, or false if it is unavailable or unknown.
+     * Use {@link ImsMmTelManager#isAvailable(int, int)} instead.
      * @hide
      */
     @UnsupportedAppUsage
@@ -8708,6 +8909,7 @@
      * Returns the Status of Wi-Fi calling (Voice over WiFi) for the subscription ID specified.
      * @param subId the subscription ID.
      * @return true if VoWiFi is available, or false if it is unavailable or unknown.
+     * Use {@link ImsMmTelManager#isAvailable(int, int)} instead.
      * @hide
      */
     @UnsupportedAppUsage
@@ -8728,6 +8930,7 @@
      *  - {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} for IWLAN registration, or
      *  - {@link ImsRegistrationImplBase#REGISTRATION_TECH_NONE} if we are not registered or the
      *  result is unavailable.
+     *  Use {@link ImsMmTelManager.RegistrationCallback} instead.
      *  @hide
      */
     public @ImsRegistrationImplBase.ImsRegistrationTech int getImsRegTechnologyForMmTel() {
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 37a4491..93ccba1 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,10 +15,12 @@
  */
 package android.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.pm.PackageInfo;
 import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -168,17 +170,28 @@
      *
      * @param packageInfo package info fetched from
      *     {@link android.content.pm.PackageManager#getPackageInfo}.
-     *     {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in.
+     *     {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES} must have been
+     *         passed in.
      * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or
      *     {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}.
      */
     public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
-        if (packageInfo.signatures == null || packageInfo.signatures.length == 0) {
-            throw new IllegalArgumentException(
-                    "Must use GET_SIGNATURES when looking up package info");
+        Signature[] signatures = packageInfo.signatures;
+        SigningInfo sInfo = packageInfo.signingInfo;
+
+        if (sInfo != null) {
+            signatures = sInfo.getSigningCertificateHistory();
+            if (sInfo.hasMultipleSigners()) {
+                signatures = sInfo.getApkContentsSigners();
+            }
         }
 
-        for (Signature sig : packageInfo.signatures) {
+        if (signatures == null || signatures.length == 0) {
+            throw new IllegalArgumentException(
+                    "Must use GET_SIGNING_CERTIFICATES when looking up package info");
+        }
+
+        for (Signature sig : signatures) {
             int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName);
             if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
                 return accessStatus;
@@ -213,7 +226,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -236,6 +249,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " +
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index fb1da7b..a0e949a 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -16,6 +16,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -179,7 +181,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -210,6 +212,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UiccSlotInfo (mIsActive="
diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
index 010ad2b..5f2f75d 100644
--- a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
+++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
@@ -16,6 +16,7 @@
 
 package android.telephony.cdma;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +29,7 @@
  *
  * {@hide}
  */
-public class CdmaSmsCbProgramData implements Parcelable {
+public final class CdmaSmsCbProgramData implements Parcelable {
 
     /** Delete the specified service category from the list of enabled categories. */
     public static final int OPERATION_DELETE_CATEGORY   = 0;
@@ -95,7 +96,7 @@
 
     /** Create a new CdmaSmsCbProgramData object with the specified values. */
     public CdmaSmsCbProgramData(int operation, int category, int language, int maxMessages,
-            int alertOption, String categoryName) {
+            int alertOption, @NonNull String categoryName) {
         mOperation = operation;
         mCategory = category;
         mLanguage = language;
@@ -174,6 +175,7 @@
      * Returns the service category name, in the language specified by {@link #getLanguage()}.
      * @return an optional service category name
      */
+    @NonNull
     public String getCategoryName() {
         return mCategoryName;
     }
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 17699d7..9170e88 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -213,6 +213,7 @@
      */
     public int getMtu() { return mMtu; }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
@@ -233,7 +234,7 @@
     }
 
     @Override
-    public boolean equals (Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
 
         if (!(o instanceof DataCallResponse)) {
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index c53ade1..0d79ec9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -257,6 +257,7 @@
         return 0;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType
@@ -303,7 +304,7 @@
     };
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DataProfile that = (DataProfile) o;
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index b27f775..c348cff 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -107,7 +108,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -132,6 +133,7 @@
         return result;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "EuiccNotification (seq="
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index 89842ae..d5a05ae 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,7 +29,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -204,7 +204,7 @@
     }
 
     @Override
-    public boolean equals(Object obj) {
+    public boolean equals(@Nullable Object obj) {
         if (this == obj) {
             return true;
         }
diff --git a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
index 9b72d58..79cdfef 100644
--- a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
+++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
@@ -185,6 +185,7 @@
         out.writeInt(mServiceClass);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + ", Condition: " + mCondition
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 2087914..4ddeb90 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -309,6 +309,37 @@
     public @CallRestrictCause int mRestrictCause = CALL_RESTRICT_CAUSE_NONE;
 
     /**
+     * The VERSTAT for an incoming call's phone number.
+     */
+    private @VerificationStatus int mCallerNumberVerificationStatus;
+
+    /**
+     * Indicates that the network could not perform verification.
+     */
+    public static final int VERIFICATION_STATUS_NOT_VERIFIED = 0;
+
+    /**
+     * Indicates that verification by the network passed.  This indicates there is a high likelihood
+     * that the call originated from a valid source.
+     */
+    public static final int VERIFICATION_STATUS_PASSED = 1;
+
+    /**
+     * Indicates that verification by the network failed.  This indicates there is a high likelihood
+     * that the call did not originate from a valid source.
+     */
+    public static final int VERIFICATION_STATUS_FAILED = 2;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "VERIFICATION_STATUS_", value = {
+            VERIFICATION_STATUS_NOT_VERIFIED,
+            VERIFICATION_STATUS_PASSED,
+            VERIFICATION_STATUS_FAILED
+    })
+    public @interface VerificationStatus {}
+
+    /**
      * The emergency service categories, only valid if {@link #getServiceType} returns
      * {@link #SERVICE_TYPE_EMERGENCY}
      *
@@ -539,7 +570,30 @@
         mMediaProfile = profile.mMediaProfile;
     }
 
+    /**
+     * Sets the verification status for the phone number of an incoming call as identified in
+     * ATIS-1000082.
+     * <p>
+     * The ImsService should parse the verstat information from the SIP INVITE headers for the call
+     * to determine this information.  It is typically found in the P-Asserted-Identity OR From
+     * header fields.
+     * @param callerNumberVerificationStatus the new verification status.
+     */
+    public void setCallerNumberVerificationStatus(
+            @VerificationStatus int callerNumberVerificationStatus) {
+        mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+    }
 
+    /**
+     * Gets the verification status for the phone number of an incoming call as identified in
+     * ATIS-1000082.
+     * @return the verification status.
+     */
+    public @VerificationStatus int getCallerNumberVerificationStatus() {
+        return mCallerNumberVerificationStatus;
+    }
+
+    @NonNull
     @Override
     public String toString() {
         return "{ serviceType=" + mServiceType
@@ -551,7 +605,8 @@
                 + ", emergencyCallRouting=" + mEmergencyCallRouting
                 + ", emergencyCallTesting=" + mEmergencyCallTesting
                 + ", hasKnownUserIntentEmergency=" + mHasKnownUserIntentEmergency
-                + ", mRestrictCause=" + mRestrictCause + " }";
+                + ", mRestrictCause=" + mRestrictCause
+                + ", mCallerNumberVerstat= " + mCallerNumberVerificationStatus + " }";
     }
 
     @Override
@@ -572,6 +627,7 @@
         out.writeBoolean(mEmergencyCallTesting);
         out.writeBoolean(mHasKnownUserIntentEmergency);
         out.writeInt(mRestrictCause);
+        out.writeInt(mCallerNumberVerificationStatus);
     }
 
     private void readFromParcel(Parcel in) {
@@ -585,6 +641,7 @@
         mEmergencyCallTesting = in.readBoolean();
         mHasKnownUserIntentEmergency = in.readBoolean();
         mRestrictCause = in.readInt();
+        mCallerNumberVerificationStatus = in.readInt();
     }
 
     public static final @android.annotation.NonNull Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 44595b5..6f062f4 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -176,6 +177,7 @@
         return Call.STATE_ACTIVE;
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index 37b11ed..eb2ebca 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -213,6 +213,7 @@
         return mIsHeld;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ImsExternalCallState { mCallId = " + mCallId +
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 20aba4d..1e0d9a78 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.ims;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -1183,6 +1184,8 @@
     /**
      * @return the string format of {@link ImsReasonInfo}
      */
+    @NonNull
+    @Override
     public String toString() {
         return "ImsReasonInfo :: {" + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
     }
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 32b4382..ec2ff6c 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -570,6 +570,8 @@
         return mCfInfo;
     }
 
+    @NonNull
+    @Override
     public String toString() {
         return "[ImsSsData] " + "ServiceType: " + getServiceType()
             + " RequestType: " + getRequestType()
diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java
index 9912ece..be34f9d 100644
--- a/telephony/java/android/telephony/ims/ImsSsInfo.java
+++ b/telephony/java/android/telephony/ims/ImsSsInfo.java
@@ -254,6 +254,7 @@
         out.writeInt(mClirOutgoingState);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index fd75a6b..c1f059e 100644
--- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -194,6 +195,7 @@
         mRttMode = profile.mRttMode;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "{ audioQuality=" + mAudioQuality +
diff --git a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
index 2e4288d..1630368 100644
--- a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
+++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
@@ -17,6 +17,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -67,6 +68,7 @@
         history = in.createStringArray();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "{ notificationType=" + notificationType +
diff --git a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 606df15..5aa58c1 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
@@ -21,8 +21,7 @@
  * {@hide}
  */
 oneway interface IImsSmsListener {
-    void onSendSmsResult(int token, int messageRef, int status, int reason);
-    void onSmsStatusReportReceived(int token, int messageRef, in String format,
-            in byte[] pdu);
+    void onSendSmsResult(int token, int messageRef, int status, int reason, int networkErrorCode);
+    void onSmsStatusReportReceived(int token, in String format, in byte[] pdu);
     void onSmsReceived(int token, in String format, in byte[] pdu);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 80fc09e..87a5094 100644
--- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.feature;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -98,6 +99,7 @@
             return radioTech;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "CapabilityPair{"
@@ -219,6 +221,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CapabilityChangeRequest{"
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 3a9979d..3562880 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -201,15 +201,20 @@
     }
 
     /**
-     * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
-     * <p>
-     * Typically this class is not used directly, but rather extended in subclasses of
+     * Contains the IMS capabilities defined and supported by an ImsFeature in the form of a
+     * bit-mask.
+     *
+     * @deprecated This class is not used directly, but rather extended in subclasses of
      * {@link ImsFeature} to provide service specific capabilities.
+     * @see MmTelFeature.MmTelCapabilities
      * @hide
      */
-    @SystemApi
+    // Not Actually deprecated, but we need to remove it from the @SystemApi surface.
+    @Deprecated
+    @SystemApi // SystemApi only because it was leaked through type usage in a previous release.
     public static class Capabilities {
         /** @deprecated Use getters and accessors instead. */
+        // Not actually deprecated, but we need to remove it from the @SystemApi surface eventually.
         protected int mCapabilities = 0;
 
         /**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 056a0ab..ceb4704 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -242,8 +242,8 @@
          * @param capabilities The capabilities that are supported for MmTel in the form of a
          *                     bitfield.
          */
-        public MmTelCapabilities(int capabilities) {
-            mCapabilities = capabilities;
+        public MmTelCapabilities(@MmTelCapability int capabilities) {
+            super(capabilities);
         }
 
         @IntDef(flag = true,
@@ -291,6 +291,7 @@
             return super.isCapable(capabilities);
         }
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder("MmTel Capabilities - [");
diff --git a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
index 3b298bb..cd9ebbf 100644
--- a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
+++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
@@ -16,6 +16,8 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -60,7 +62,7 @@
         }
 
         @Override
-        public boolean equals(Object o) {
+        public boolean equals(@Nullable Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
 
@@ -77,6 +79,7 @@
             return result;
         }
 
+        @NonNull
         @Override
         public String toString() {
             return "{s=" + slotId + ", f=" + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "}";
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 852c8e0..175769b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -118,6 +118,12 @@
      */
     public static final int STATUS_REPORT_STATUS_ERROR = 2;
 
+    /**
+     * No network error was generated while processing the SMS message.
+     */
+    // Should match SmsResponse.NO_ERROR_CODE
+    public static final int RESULT_NO_NETWORK_ERROR = -1;
+
     // Lock for feature synchronization
     private final Object mLock = new Object();
     private IImsSmsListener mListener;
@@ -147,7 +153,7 @@
      *               {@link SmsMessage#FORMAT_3GPP2}.
      * @param smsc the Short Message Service Center address.
      * @param isRetry whether it is a retry of an already attempted message or not.
-     * @param pdu PDUs representing the contents of the message.
+     * @param pdu PDU representing the contents of the message.
      */
     public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
             byte[] pdu) {
@@ -166,27 +172,29 @@
      * provider.
      *
      * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
+     * @param messageRef the message reference
      * @param result result of delivering the message. Valid values are:
      *  {@link #DELIVER_STATUS_OK},
      *  {@link #DELIVER_STATUS_ERROR_GENERIC},
      *  {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
      *  {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
-     * @param messageRef the message reference
      */
-    public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
+    public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
         Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
     }
 
     /**
      * This method will be triggered by the platform after
-     * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+     * {@link #onSmsStatusReportReceived(int, int, String, byte[])} or
+     * {@link #onSmsStatusReportReceived(int, String, byte[])} has been called to provide the
      * result to the IMS provider.
      *
-     * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+     * @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
+     *              or {@link #onSmsStatusReportReceived(int, String, byte[])}
+     * @param messageRef the message reference
      * @param result result of delivering the message. Valid values are:
      *  {@link #STATUS_REPORT_STATUS_OK},
      *  {@link #STATUS_REPORT_STATUS_ERROR}
-     * @param messageRef the message reference
      */
     public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
         Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
@@ -204,7 +212,7 @@
      *              callbacks for this message.
      * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
      * {@link SmsMessage#FORMAT_3GPP2}.
-     * @param pdu PDUs representing the contents of the message.
+     * @param pdu PDU representing the contents of the message.
      * @throws RuntimeException if called before {@link #onReady()} is triggered.
      */
     public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
@@ -229,16 +237,37 @@
     }
 
     /**
+     * This method should be triggered by the IMS providers when an outgoing SMS message has been
+     * sent successfully.
+     *
+     * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+     * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+     *
+     * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
+     * connection to the framework is not available. If this happens attempting to send the SMS
+     * should be aborted.
+     */
+    public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException {
+        synchronized (mLock) {
+            if (mListener == null) {
+                throw new RuntimeException("Feature not ready.");
+            }
+            try {
+                mListener.onSendSmsResult(token, messageRef, SEND_STATUS_OK,
+                        SmsManager.RESULT_ERROR_NONE, RESULT_NO_NETWORK_ERROR);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * This method should be triggered by the IMS providers to pass the result of the sent message
      * to the platform.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
      * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
-     * @param status result of sending the SMS. Valid values are:
-     *  {@link #SEND_STATUS_OK},
-     *  {@link #SEND_STATUS_ERROR},
-     *  {@link #SEND_STATUS_ERROR_RETRY},
-     *  {@link #SEND_STATUS_ERROR_FALLBACK},
+     * @param status result of sending the SMS.
      * @param reason reason in case status is failure. Valid values are:
      *  {@link SmsManager#RESULT_ERROR_NONE},
      *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
@@ -269,7 +298,11 @@
      * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
      * connection to the framework is not available. If this happens attempting to send the SMS
      * should be aborted.
+     * @deprecated Use {@link #onSendSmsResultSuccess(int, int)} or
+     * {@link #onSendSmsResultError(int, int, int, int, int)} to notify the framework of the SMS
+     * send result.
      */
+    @Deprecated
     public final void onSendSmsResult(int token, int messageRef,  @SendStatusResult int status,
             int reason) throws RuntimeException {
         synchronized (mLock) {
@@ -277,7 +310,8 @@
                 throw new RuntimeException("Feature not ready.");
             }
             try {
-                mListener.onSendSmsResult(token, messageRef, status, reason);
+                mListener.onSendSmsResult(token, messageRef, status, reason,
+                        RESULT_NO_NETWORK_ERROR);
             } catch (RemoteException e) {
                 e.rethrowFromSystemServer();
             }
@@ -285,23 +319,89 @@
     }
 
     /**
-     * Sets the status report of the sent message.
+     * This method should be triggered by the IMS providers when an outgoing message fails to be
+     * sent due to an error generated while processing the message or after being sent to the
+     * network.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param messageRef the message reference.
-     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
-     * {@link SmsMessage#FORMAT_3GPP2}.
-     * @param pdu PDUs representing the content of the status report.
-     * @throws RuntimeException if called before {@link #onReady()} is triggered
+     * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
+     * @param status result of sending the SMS.
+     * @param reason Valid values are:
+     *  {@link SmsManager#RESULT_ERROR_NONE},
+     *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
+     *  {@link SmsManager#RESULT_ERROR_RADIO_OFF},
+     *  {@link SmsManager#RESULT_ERROR_NULL_PDU},
+     *  {@link SmsManager#RESULT_ERROR_NO_SERVICE},
+     *  {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+     *  {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
+     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
+     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
+     *  {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
+     *  {@link SmsManager#RESULT_NETWORK_REJECT},
+     *  {@link SmsManager#RESULT_INVALID_ARGUMENTS},
+     *  {@link SmsManager#RESULT_INVALID_STATE},
+     *  {@link SmsManager#RESULT_NO_MEMORY},
+     *  {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
+     *  {@link SmsManager#RESULT_SYSTEM_ERROR},
+     *  {@link SmsManager#RESULT_MODEM_ERROR},
+     *  {@link SmsManager#RESULT_NETWORK_ERROR},
+     *  {@link SmsManager#RESULT_ENCODING_ERROR},
+     *  {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
+     *  {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
+     *  {@link SmsManager#RESULT_INTERNAL_ERROR},
+     *  {@link SmsManager#RESULT_NO_RESOURCES},
+     *  {@link SmsManager#RESULT_CANCELLED},
+     *  {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+     * @param networkErrorCode the error code reported by the carrier network if sending this SMS
+     *  has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was
+     *  generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information.
+     *
+     * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
+     * connection to the framework is not available. If this happens attempting to send the SMS
+     * should be aborted.
      */
-    public final void onSmsStatusReportReceived(int token, int messageRef, String format,
-            byte[] pdu) throws RuntimeException{
+    public final void onSendSmsResultError(int token, int messageRef,  @SendStatusResult int status,
+            int reason, int networkErrorCode)
+            throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
                 throw new RuntimeException("Feature not ready.");
             }
             try {
-                mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
+                mListener.onSendSmsResult(token, messageRef, status, reason, networkErrorCode);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * This method should be triggered by the IMS providers when the status report of the sent
+     * message is received. The platform will handle the report and notify the IMS provider of the
+     * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
+     *
+     * This method must not be called before {@link #onReady()} is called or the call will fail. If
+     * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+     * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
+     * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
+     * @param messageRef the message reference.
+     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+     *               {@link SmsMessage#FORMAT_3GPP2}.
+     * @param pdu PDU representing the content of the status report.
+     * @throws RuntimeException if called before {@link #onReady()} is triggered
+     *
+     * @deprecated Use {@link #onSmsStatusReportReceived(int, String, byte[])} instead without the
+     * message reference.
+     */
+    @Deprecated
+    public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+            byte[] pdu) throws RuntimeException {
+        synchronized (mLock) {
+            if (mListener == null) {
+                throw new RuntimeException("Feature not ready.");
+            }
+            try {
+                mListener.onSmsStatusReportReceived(token, format, pdu);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
                 acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
@@ -310,6 +410,46 @@
     }
 
     /**
+     * This method should be triggered by the IMS providers when the status report of the sent
+     * message is received. The platform will handle the report and notify the IMS provider of the
+     * result by calling {@link #acknowledgeSmsReport(int, int, int)}.
+     *
+     * This method must not be called before {@link #onReady()} is called or the call will fail. If
+     * the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
+     * with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
+     * @param token unique token generated by IMS providers that the platform will use to trigger
+     *              callbacks for this message.
+     * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+     *               {@link SmsMessage#FORMAT_3GPP2}.
+     * @param pdu PDU representing the content of the status report.
+     * @throws RuntimeException if called before {@link #onReady()} is triggered
+     */
+    public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
+            throws RuntimeException {
+        synchronized (mLock) {
+            if (mListener == null) {
+                throw new RuntimeException("Feature not ready.");
+            }
+            try {
+                mListener.onSmsStatusReportReceived(token, format, pdu);
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
+                SmsMessage message = SmsMessage.createFromPdu(pdu, format);
+                if (message != null && message.mWrappedSmsMessage != null) {
+                    acknowledgeSmsReport(
+                            token,
+                            message.mWrappedSmsMessage.mMessageRef,
+                            STATUS_REPORT_STATUS_ERROR);
+                } else {
+                    Log.w(LOG_TAG,
+                            "onSmsStatusReportReceivedWithoutMessageRef: Invalid pdu entered.");
+                    acknowledgeSmsReport(token, 0, STATUS_REPORT_STATUS_ERROR);
+                }
+            }
+        }
+    }
+
+    /**
      * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
      * Provider.
      *
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 52b51d2..ac258cd 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Intent;
@@ -381,7 +382,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) return true;
         if (o == null) {
             return false;
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
deleted file mode 100644
index 13539b8..0000000
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.location.Country;
-import android.location.CountryDetector;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
-import android.provider.ContactsContract.RawContacts;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.i18n.phonenumbers.NumberParseException;
-import com.android.i18n.phonenumbers.PhoneNumberUtil;
-import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
-import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
-
-import java.util.Locale;
-
-
-/**
- * Looks up caller information for the given phone number.
- *
- * {@hide}
- */
-public class CallerInfo {
-    private static final String TAG = "CallerInfo";
-    private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
-
-    public static final long USER_TYPE_CURRENT = 0;
-    public static final long USER_TYPE_WORK = 1;
-
-    /**
-     * Please note that, any one of these member variables can be null,
-     * and any accesses to them should be prepared to handle such a case.
-     *
-     * Also, it is implied that phoneNumber is more often populated than
-     * name is, (think of calls being dialed/received using numbers where
-     * names are not known to the device), so phoneNumber should serve as
-     * a dependable fallback when name is unavailable.
-     *
-     * One other detail here is that this CallerInfo object reflects
-     * information found on a connection, it is an OUTPUT that serves
-     * mainly to display information to the user.  In no way is this object
-     * used as input to make a connection, so we can choose to display
-     * whatever human-readable text makes sense to the user for a
-     * connection.  This is especially relevant for the phone number field,
-     * since it is the one field that is most likely exposed to the user.
-     *
-     * As an example:
-     *   1. User dials "911"
-     *   2. Device recognizes that this is an emergency number
-     *   3. We use the "Emergency Number" string instead of "911" in the
-     *     phoneNumber field.
-     *
-     * What we're really doing here is treating phoneNumber as an essential
-     * field here, NOT name.  We're NOT always guaranteed to have a name
-     * for a connection, but the number should be displayable.
-     */
-    @UnsupportedAppUsage
-    public String name;
-    @UnsupportedAppUsage
-    public String phoneNumber;
-    public String normalizedNumber;
-    public String geoDescription;
-
-    public String cnapName;
-    public int numberPresentation;
-    public int namePresentation;
-    public boolean contactExists;
-
-    public String phoneLabel;
-    /* Split up the phoneLabel into number type and label name */
-    @UnsupportedAppUsage
-    public int    numberType;
-    @UnsupportedAppUsage
-    public String numberLabel;
-
-    public int photoResource;
-
-    // Contact ID, which will be 0 if a contact comes from the corp CP2.
-    @UnsupportedAppUsage
-    public long contactIdOrZero;
-    public boolean needUpdate;
-    public Uri contactRefUri;
-    public String lookupKey;
-
-    public ComponentName preferredPhoneAccountComponent;
-    public String preferredPhoneAccountId;
-
-    public long userType;
-
-    /**
-     * Contact display photo URI.  If a contact has no display photo but a thumbnail, it'll be
-     * the thumbnail URI instead.
-     */
-    public Uri contactDisplayPhotoUri;
-
-    // fields to hold individual contact preference data,
-    // including the send to voicemail flag and the ringtone
-    // uri reference.
-    public Uri contactRingtoneUri;
-    public boolean shouldSendToVoicemail;
-
-    /**
-     * Drawable representing the caller image.  This is essentially
-     * a cache for the image data tied into the connection /
-     * callerinfo object.
-     *
-     * This might be a high resolution picture which is more suitable
-     * for full-screen image view than for smaller icons used in some
-     * kinds of notifications.
-     *
-     * The {@link #isCachedPhotoCurrent} flag indicates if the image
-     * data needs to be reloaded.
-     */
-    public Drawable cachedPhoto;
-    /**
-     * Bitmap representing the caller image which has possibly lower
-     * resolution than {@link #cachedPhoto} and thus more suitable for
-     * icons (like notification icons).
-     *
-     * In usual cases this is just down-scaled image of {@link #cachedPhoto}.
-     * If the down-scaling fails, this will just become null.
-     *
-     * The {@link #isCachedPhotoCurrent} flag indicates if the image
-     * data needs to be reloaded.
-     */
-    public Bitmap cachedPhotoIcon;
-    /**
-     * Boolean which indicates if {@link #cachedPhoto} and
-     * {@link #cachedPhotoIcon} is fresh enough. If it is false,
-     * those images aren't pointing to valid objects.
-     */
-    public boolean isCachedPhotoCurrent;
-
-    private boolean mIsEmergency;
-    private boolean mIsVoiceMail;
-
-    @UnsupportedAppUsage
-    public CallerInfo() {
-        // TODO: Move all the basic initialization here?
-        mIsEmergency = false;
-        mIsVoiceMail = false;
-        userType = USER_TYPE_CURRENT;
-    }
-
-    /**
-     * getCallerInfo given a Cursor.
-     * @param context the context used to retrieve string constants
-     * @param contactRef the URI to attach to this CallerInfo object
-     * @param cursor the first object in the cursor is used to build the CallerInfo object.
-     * @return the CallerInfo which contains the caller id for the given
-     * number. The returned CallerInfo is null if no number is supplied.
-     */
-    public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
-        CallerInfo info = new CallerInfo();
-        info.photoResource = 0;
-        info.phoneLabel = null;
-        info.numberType = 0;
-        info.numberLabel = null;
-        info.cachedPhoto = null;
-        info.isCachedPhotoCurrent = false;
-        info.contactExists = false;
-        info.userType = USER_TYPE_CURRENT;
-
-        if (VDBG) Rlog.v(TAG, "getCallerInfo() based on cursor...");
-
-        if (cursor != null) {
-            if (cursor.moveToFirst()) {
-                // TODO: photo_id is always available but not taken
-                // care of here. Maybe we should store it in the
-                // CallerInfo object as well.
-
-                int columnIndex;
-
-                // Look for the name
-                columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
-                if (columnIndex != -1) {
-                    info.name = cursor.getString(columnIndex);
-                }
-
-                // Look for the number
-                columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
-                if (columnIndex != -1) {
-                    info.phoneNumber = cursor.getString(columnIndex);
-                }
-
-                // Look for the normalized number
-                columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
-                if (columnIndex != -1) {
-                    info.normalizedNumber = cursor.getString(columnIndex);
-                }
-
-                // Look for the label/type combo
-                columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
-                if (columnIndex != -1) {
-                    int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
-                    if (typeColumnIndex != -1) {
-                        info.numberType = cursor.getInt(typeColumnIndex);
-                        info.numberLabel = cursor.getString(columnIndex);
-                        info.phoneLabel = Phone.getDisplayLabel(context,
-                                info.numberType, info.numberLabel)
-                                .toString();
-                    }
-                }
-
-                // Look for the person_id.
-                columnIndex = getColumnIndexForPersonId(contactRef, cursor);
-                if (columnIndex != -1) {
-                    final long contactId = cursor.getLong(columnIndex);
-                    if (contactId != 0 && !Contacts.isEnterpriseContactId(contactId)) {
-                        info.contactIdOrZero = contactId;
-                        if (VDBG) {
-                            Rlog.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
-                        }
-                    }
-                    if (Contacts.isEnterpriseContactId(contactId)) {
-                        info.userType = USER_TYPE_WORK;
-                    }
-                } else {
-                    // No valid columnIndex, so we can't look up person_id.
-                    Rlog.w(TAG, "Couldn't find contact_id column for " + contactRef);
-                    // Watch out: this means that anything that depends on
-                    // person_id will be broken (like contact photo lookups in
-                    // the in-call UI, for example.)
-                }
-
-                // Contact lookupKey
-                columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
-                if (columnIndex != -1) {
-                    info.lookupKey = cursor.getString(columnIndex);
-                }
-
-                // Display photo URI.
-                columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
-                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-                    info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
-                } else {
-                    info.contactDisplayPhotoUri = null;
-                }
-
-                columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_COMPONENT_NAME);
-                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-                    info.preferredPhoneAccountComponent =
-                            ComponentName.unflattenFromString(cursor.getString(columnIndex));
-                }
-
-                columnIndex = cursor.getColumnIndex(Data.PREFERRED_PHONE_ACCOUNT_ID);
-                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-                    info.preferredPhoneAccountId = cursor.getString(columnIndex);
-                }
-
-                // look for the custom ringtone, create from the string stored
-                // in the database.
-                // An empty string ("") in the database indicates a silent ringtone,
-                // and we set contactRingtoneUri = Uri.EMPTY, so that no ringtone will be played.
-                // {null} in the database indicates the default ringtone,
-                // and we set contactRingtoneUri = null, so that default ringtone will be played.
-                columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
-                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-                    if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
-                        info.contactRingtoneUri = Uri.EMPTY;
-                    } else {
-                        info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
-                    }
-                } else {
-                    info.contactRingtoneUri = null;
-                }
-
-                // look for the send to voicemail flag, set it to true only
-                // under certain circumstances.
-                columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
-                info.shouldSendToVoicemail = (columnIndex != -1) &&
-                        ((cursor.getInt(columnIndex)) == 1);
-                info.contactExists = true;
-            }
-            cursor.close();
-            cursor = null;
-        }
-
-        info.needUpdate = false;
-        info.name = normalize(info.name);
-        info.contactRefUri = contactRef;
-
-        return info;
-    }
-
-    /**
-     * getCallerInfo given a URI, look up in the call-log database
-     * for the uri unique key.
-     * @param context the context used to get the ContentResolver
-     * @param contactRef the URI used to lookup caller id
-     * @return the CallerInfo which contains the caller id for the given
-     * number. The returned CallerInfo is null if no number is supplied.
-     */
-    @UnsupportedAppUsage
-    public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
-        CallerInfo info = null;
-        ContentResolver cr = CallerInfoAsyncQuery.getCurrentProfileContentResolver(context);
-        if (cr != null) {
-            try {
-                info = getCallerInfo(context, contactRef,
-                        cr.query(contactRef, null, null, null, null));
-            } catch (RuntimeException re) {
-                Rlog.e(TAG, "Error getting caller info.", re);
-            }
-        }
-        return info;
-    }
-
-    /**
-     * getCallerInfo given a phone number, look up in the call-log database
-     * for the matching caller id info.
-     * @param context the context used to get the ContentResolver
-     * @param number the phone number used to lookup caller id
-     * @return the CallerInfo which contains the caller id for the given
-     * number. The returned CallerInfo is null if no number is supplied. If
-     * a matching number is not found, then a generic caller info is returned,
-     * with all relevant fields empty or null.
-     */
-    @UnsupportedAppUsage
-    public static CallerInfo getCallerInfo(Context context, String number) {
-        if (VDBG) Rlog.v(TAG, "getCallerInfo() based on number...");
-
-        int subId = SubscriptionManager.getDefaultSubscriptionId();
-        return getCallerInfo(context, number, subId);
-    }
-
-    /**
-     * getCallerInfo given a phone number and subscription, look up in the call-log database
-     * for the matching caller id info.
-     * @param context the context used to get the ContentResolver
-     * @param number the phone number used to lookup caller id
-     * @param subId the subscription for checking for if voice mail number or not
-     * @return the CallerInfo which contains the caller id for the given
-     * number. The returned CallerInfo is null if no number is supplied. If
-     * a matching number is not found, then a generic caller info is returned,
-     * with all relevant fields empty or null.
-     */
-    @UnsupportedAppUsage
-    public static CallerInfo getCallerInfo(Context context, String number, int subId) {
-
-        if (TextUtils.isEmpty(number)) {
-            return null;
-        }
-
-        // Change the callerInfo number ONLY if it is an emergency number
-        // or if it is the voicemail number.  If it is either, take a
-        // shortcut and skip the query.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
-            return new CallerInfo().markAsEmergency(context);
-        } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
-            return new CallerInfo().markAsVoiceMail();
-        }
-
-        Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
-                Uri.encode(number));
-
-        CallerInfo info = getCallerInfo(context, contactUri);
-        info = doSecondaryLookupIfNecessary(context, number, info);
-
-        // if no query results were returned with a viable number,
-        // fill in the original number value we used to query with.
-        if (TextUtils.isEmpty(info.phoneNumber)) {
-            info.phoneNumber = number;
-        }
-
-        return info;
-    }
-
-    /**
-     * Performs another lookup if previous lookup fails and it's a SIP call
-     * and the peer's username is all numeric. Look up the username as it
-     * could be a PSTN number in the contact database.
-     *
-     * @param context the query context
-     * @param number the original phone number, could be a SIP URI
-     * @param previousResult the result of previous lookup
-     * @return previousResult if it's not the case
-     */
-    static CallerInfo doSecondaryLookupIfNecessary(Context context,
-            String number, CallerInfo previousResult) {
-        if (!previousResult.contactExists
-                && PhoneNumberUtils.isUriNumber(number)) {
-            String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
-            if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
-                previousResult = getCallerInfo(context,
-                        Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
-                                Uri.encode(username)));
-            }
-        }
-        return previousResult;
-    }
-
-    // Accessors
-
-    /**
-     * @return true if the caller info is an emergency number.
-     */
-    public boolean isEmergencyNumber() {
-        return mIsEmergency;
-    }
-
-    /**
-     * @return true if the caller info is a voicemail number.
-     */
-    public boolean isVoiceMailNumber() {
-        return mIsVoiceMail;
-    }
-
-    /**
-     * Mark this CallerInfo as an emergency call.
-     * @param context To lookup the localized 'Emergency Number' string.
-     * @return this instance.
-     */
-    // TODO: Note we're setting the phone number here (refer to
-    // javadoc comments at the top of CallerInfo class) to a localized
-    // string 'Emergency Number'. This is pretty bad because we are
-    // making UI work here instead of just packaging the data. We
-    // should set the phone number to the dialed number and name to
-    // 'Emergency Number' and let the UI make the decision about what
-    // should be displayed.
-    /* package */ CallerInfo markAsEmergency(Context context) {
-        phoneNumber = context.getString(
-            com.android.internal.R.string.emergency_call_dialog_number_for_display);
-        photoResource = com.android.internal.R.drawable.picture_emergency;
-        mIsEmergency = true;
-        return this;
-    }
-
-
-    /**
-     * Mark this CallerInfo as a voicemail call. The voicemail label
-     * is obtained from the telephony manager. Caller must hold the
-     * READ_PHONE_STATE permission otherwise the phoneNumber will be
-     * set to null.
-     * @return this instance.
-     */
-    // TODO: As in the emergency number handling, we end up writing a
-    // string in the phone number field.
-    /* package */ CallerInfo markAsVoiceMail() {
-
-        int subId = SubscriptionManager.getDefaultSubscriptionId();
-        return markAsVoiceMail(subId);
-
-    }
-
-    /* package */ CallerInfo markAsVoiceMail(int subId) {
-        mIsVoiceMail = true;
-
-        try {
-            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag(subId);
-
-            phoneNumber = voiceMailLabel;
-        } catch (SecurityException se) {
-            // Should never happen: if this process does not have
-            // permission to retrieve VM tag, it should not have
-            // permission to retrieve VM number and would not call
-            // this method.
-            // Leave phoneNumber untouched.
-            Rlog.e(TAG, "Cannot access VoiceMail.", se);
-        }
-        // TODO: There is no voicemail picture?
-        // FIXME: FIND ANOTHER ICON
-        // photoResource = android.R.drawable.badge_voicemail;
-        return this;
-    }
-
-    private static String normalize(String s) {
-        if (s == null || s.length() > 0) {
-            return s;
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the column index to use to find the "person_id" field in
-     * the specified cursor, based on the contact URI that was originally
-     * queried.
-     *
-     * This is a helper function for the getCallerInfo() method that takes
-     * a Cursor.  Looking up the person_id is nontrivial (compared to all
-     * the other CallerInfo fields) since the column we need to use
-     * depends on what query we originally ran.
-     *
-     * Watch out: be sure to not do any database access in this method, since
-     * it's run from the UI thread (see comments below for more info.)
-     *
-     * @return the columnIndex to use (with cursor.getLong()) to get the
-     * person_id, or -1 if we couldn't figure out what colum to use.
-     *
-     * TODO: Add a unittest for this method.  (This is a little tricky to
-     * test, since we'll need a live contacts database to test against,
-     * preloaded with at least some phone numbers and SIP addresses.  And
-     * we'll probably have to hardcode the column indexes we expect, so
-     * the test might break whenever the contacts schema changes.  But we
-     * can at least make sure we handle all the URI patterns we claim to,
-     * and that the mime types match what we expect...)
-     */
-    private static int getColumnIndexForPersonId(Uri contactRef, Cursor cursor) {
-        // TODO: This is pretty ugly now, see bug 2269240 for
-        // more details. The column to use depends upon the type of URL:
-        // - content://com.android.contacts/data/phones ==> use the "contact_id" column
-        // - content://com.android.contacts/phone_lookup ==> use the "_ID" column
-        // - content://com.android.contacts/data ==> use the "contact_id" column
-        // If it's none of the above, we leave columnIndex=-1 which means
-        // that the person_id field will be left unset.
-        //
-        // The logic here *used* to be based on the mime type of contactRef
-        // (for example Phone.CONTENT_ITEM_TYPE would tell us to use the
-        // RawContacts.CONTACT_ID column).  But looking up the mime type requires
-        // a call to context.getContentResolver().getType(contactRef), which
-        // isn't safe to do from the UI thread since it can cause an ANR if
-        // the contacts provider is slow or blocked (like during a sync.)
-        //
-        // So instead, figure out the column to use for person_id by just
-        // looking at the URI itself.
-
-        if (VDBG) Rlog.v(TAG, "- getColumnIndexForPersonId: contactRef URI = '"
-                        + contactRef + "'...");
-        // Warning: Do not enable the following logging (due to ANR risk.)
-        // if (VDBG) Rlog.v(TAG, "- MIME type: "
-        //                 + context.getContentResolver().getType(contactRef));
-
-        String url = contactRef.toString();
-        String columnName = null;
-        if (url.startsWith("content://com.android.contacts/data/phones")) {
-            // Direct lookup in the Phone table.
-            // MIME type: Phone.CONTENT_ITEM_TYPE (= "vnd.android.cursor.item/phone_v2")
-            if (VDBG) Rlog.v(TAG, "'data/phones' URI; using RawContacts.CONTACT_ID");
-            columnName = RawContacts.CONTACT_ID;
-        } else if (url.startsWith("content://com.android.contacts/data")) {
-            // Direct lookup in the Data table.
-            // MIME type: Data.CONTENT_TYPE (= "vnd.android.cursor.dir/data")
-            if (VDBG) Rlog.v(TAG, "'data' URI; using Data.CONTACT_ID");
-            // (Note Data.CONTACT_ID and RawContacts.CONTACT_ID are equivalent.)
-            columnName = Data.CONTACT_ID;
-        } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
-            // Lookup in the PhoneLookup table, which provides "fuzzy matching"
-            // for phone numbers.
-            // MIME type: PhoneLookup.CONTENT_TYPE (= "vnd.android.cursor.dir/phone_lookup")
-            if (VDBG) Rlog.v(TAG, "'phone_lookup' URI; using PhoneLookup._ID");
-            columnName = PhoneLookup._ID;
-        } else {
-            Rlog.w(TAG, "Unexpected prefix for contactRef '" + url + "'");
-        }
-        int columnIndex = (columnName != null) ? cursor.getColumnIndex(columnName) : -1;
-        if (VDBG) Rlog.v(TAG, "==> Using column '" + columnName
-                        + "' (columnIndex = " + columnIndex + ") for person_id lookup...");
-        return columnIndex;
-    }
-
-    /**
-     * Updates this CallerInfo's geoDescription field, based on the raw
-     * phone number in the phoneNumber field.
-     *
-     * (Note that the various getCallerInfo() methods do *not* set the
-     * geoDescription automatically; you need to call this method
-     * explicitly to get it.)
-     *
-     * @param context the context used to look up the current locale / country
-     * @param fallbackNumber if this CallerInfo's phoneNumber field is empty,
-     *        this specifies a fallback number to use instead.
-     */
-    public void updateGeoDescription(Context context, String fallbackNumber) {
-        String number = TextUtils.isEmpty(phoneNumber) ? fallbackNumber : phoneNumber;
-        geoDescription = getGeoDescription(context, number);
-    }
-
-    /**
-     * @return a geographical description string for the specified number.
-     * @see com.android.i18n.phonenumbers.PhoneNumberOfflineGeocoder
-     */
-    public static String getGeoDescription(Context context, String number) {
-        if (VDBG) Rlog.v(TAG, "getGeoDescription('" + number + "')...");
-
-        if (TextUtils.isEmpty(number)) {
-            return null;
-        }
-
-        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
-        PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance();
-
-        Locale locale = context.getResources().getConfiguration().locale;
-        String countryIso = getCurrentCountryIso(context, locale);
-        PhoneNumber pn = null;
-        try {
-            if (VDBG) Rlog.v(TAG, "parsing '" + number
-                            + "' for countryIso '" + countryIso + "'...");
-            pn = util.parse(number, countryIso);
-            if (VDBG) Rlog.v(TAG, "- parsed number: " + pn);
-        } catch (NumberParseException e) {
-            Rlog.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
-                    + Rlog.pii(TAG, number) + "'");
-        }
-
-        if (pn != null) {
-            String description = geocoder.getDescriptionForNumber(pn, locale);
-            if (VDBG) Rlog.v(TAG, "- got description: '" + description + "'");
-            return description;
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * @return The ISO 3166-1 two letters country code of the country the user
-     *         is in.
-     */
-    private static String getCurrentCountryIso(Context context, Locale locale) {
-        String countryIso = null;
-        CountryDetector detector = (CountryDetector) context.getSystemService(
-                Context.COUNTRY_DETECTOR);
-        if (detector != null) {
-            Country country = detector.detectCountry();
-            if (country != null) {
-                countryIso = country.getCountryIso();
-            } else {
-                Rlog.e(TAG, "CountryDetector.detectCountry() returned null.");
-            }
-        }
-        if (countryIso == null) {
-            countryIso = locale.getCountry();
-            Rlog.w(TAG, "No CountryDetector; falling back to countryIso based on locale: "
-                    + countryIso);
-        }
-        return countryIso;
-    }
-
-    protected static String getCurrentCountryIso(Context context) {
-        return getCurrentCountryIso(context, Locale.getDefault());
-    }
-
-    /**
-     * @return a string debug representation of this instance.
-     */
-    @Override
-    public String toString() {
-        // Warning: never check in this file with VERBOSE_DEBUG = true
-        // because that will result in PII in the system log.
-        final boolean VERBOSE_DEBUG = false;
-
-        if (VERBOSE_DEBUG) {
-            return new StringBuilder(384)
-                    .append(super.toString() + " { ")
-                    .append("\nname: " + name)
-                    .append("\nphoneNumber: " + phoneNumber)
-                    .append("\nnormalizedNumber: " + normalizedNumber)
-                    .append("\ngeoDescription: " + geoDescription)
-                    .append("\ncnapName: " + cnapName)
-                    .append("\nnumberPresentation: " + numberPresentation)
-                    .append("\nnamePresentation: " + namePresentation)
-                    .append("\ncontactExits: " + contactExists)
-                    .append("\nphoneLabel: " + phoneLabel)
-                    .append("\nnumberType: " + numberType)
-                    .append("\nnumberLabel: " + numberLabel)
-                    .append("\nphotoResource: " + photoResource)
-                    .append("\ncontactIdOrZero: " + contactIdOrZero)
-                    .append("\nneedUpdate: " + needUpdate)
-                    .append("\ncontactRingtoneUri: " + contactRingtoneUri)
-                    .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
-                    .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
-                    .append("\ncachedPhoto: " + cachedPhoto)
-                    .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
-                    .append("\nemergency: " + mIsEmergency)
-                    .append("\nvoicemail " + mIsVoiceMail)
-                    .append("\ncontactExists " + contactExists)
-                    .append("\nuserType " + userType)
-                    .append(" }")
-                    .toString();
-        } else {
-            return new StringBuilder(128)
-                    .append(super.toString() + " { ")
-                    .append("name " + ((name == null) ? "null" : "non-null"))
-                    .append(", phoneNumber " + ((phoneNumber == null) ? "null" : "non-null"))
-                    .append(" }")
-                    .toString();
-        }
-    }
-}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
deleted file mode 100644
index 4e1ff8f..0000000
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.app.ActivityManager;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.text.TextUtils;
-
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper class to make it easier to run asynchronous caller-id lookup queries.
- * @see CallerInfo
- *
- * {@hide}
- */
-public class CallerInfoAsyncQuery {
-    private static final boolean DBG = false;
-    private static final String LOG_TAG = "CallerInfoAsyncQuery";
-
-    private static final int EVENT_NEW_QUERY = 1;
-    private static final int EVENT_ADD_LISTENER = 2;
-    private static final int EVENT_END_OF_QUEUE = 3;
-    private static final int EVENT_EMERGENCY_NUMBER = 4;
-    private static final int EVENT_VOICEMAIL_NUMBER = 5;
-    private static final int EVENT_GET_GEO_DESCRIPTION = 6;
-
-    private CallerInfoAsyncQueryHandler mHandler;
-
-    // If the CallerInfo query finds no contacts, should we use the
-    // PhoneNumberOfflineGeocoder to look up a "geo description"?
-    // (TODO: This could become a flag in config.xml if it ever needs to be
-    // configured on a per-product basis.)
-    private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = true;
-
-    /**
-     * Interface for a CallerInfoAsyncQueryHandler result return.
-     */
-    public interface OnQueryCompleteListener {
-        /**
-         * Called when the query is complete.
-         */
-        public void onQueryComplete(int token, Object cookie, CallerInfo ci);
-    }
-
-
-    /**
-     * Wrap the cookie from the WorkerArgs with additional information needed by our
-     * classes.
-     */
-    private static final class CookieWrapper {
-        @UnsupportedAppUsage
-        private CookieWrapper() {
-        }
-
-        public OnQueryCompleteListener listener;
-        public Object cookie;
-        public int event;
-        public String number;
-        public String geoDescription;
-
-        public int subId;
-    }
-
-
-    /**
-     * Simple exception used to communicate problems with the query pool.
-     */
-    public static class QueryPoolException extends SQLException {
-        public QueryPoolException(String error) {
-            super(error);
-        }
-    }
-
-    /**
-     * @return {@link ContentResolver} for the "current" user.
-     */
-    static ContentResolver getCurrentProfileContentResolver(Context context) {
-
-        if (DBG) Rlog.d(LOG_TAG, "Trying to get current content resolver...");
-
-        final int currentUser = ActivityManager.getCurrentUser();
-        final int myUser = UserManager.get(context).getUserHandle();
-
-        if (DBG) Rlog.d(LOG_TAG, "myUser=" + myUser + "currentUser=" + currentUser);
-
-        if (myUser != currentUser) {
-            final Context otherContext;
-            try {
-                otherContext = context.createPackageContextAsUser(context.getPackageName(),
-                        /* flags =*/ 0, UserHandle.of(currentUser));
-                return otherContext.getContentResolver();
-            } catch (NameNotFoundException e) {
-                Rlog.e(LOG_TAG, "Can't find self package", e);
-                // Fall back to the primary user.
-            }
-        }
-        return context.getContentResolver();
-    }
-
-    /**
-     * Our own implementation of the AsyncQueryHandler.
-     */
-    private class CallerInfoAsyncQueryHandler extends AsyncQueryHandler {
-
-        /*
-         * The information relevant to each CallerInfo query.  Each query may have multiple
-         * listeners, so each AsyncCursorInfo is associated with 2 or more CookieWrapper
-         * objects in the queue (one with a new query event, and one with a end event, with
-         * 0 or more additional listeners in between).
-         */
-
-        /**
-         * Context passed by the caller.
-         *
-         * NOTE: The actual context we use for query may *not* be this context; since we query
-         * against the "current" contacts provider.  In the constructor we pass the "current"
-         * context resolver (obtained via {@link #getCurrentProfileContentResolver) and pass it
-         * to the super class.
-         */
-        private Context mContext;
-        private Uri mQueryUri;
-        private CallerInfo mCallerInfo;
-        private List<Runnable> mPendingListenerCallbacks = new ArrayList<>();
-
-        /**
-         * Our own query worker thread.
-         *
-         * This thread handles the messages enqueued in the looper.  The normal sequence
-         * of events is that a new query shows up in the looper queue, followed by 0 or
-         * more add listener requests, and then an end request.  Of course, these requests
-         * can be interlaced with requests from other tokens, but is irrelevant to this
-         * handler since the handler has no state.
-         *
-         * Note that we depend on the queue to keep things in order; in other words, the
-         * looper queue must be FIFO with respect to input from the synchronous startQuery
-         * calls and output to this handleMessage call.
-         *
-         * This use of the queue is required because CallerInfo objects may be accessed
-         * multiple times before the query is complete.  All accesses (listeners) must be
-         * queued up and informed in order when the query is complete.
-         */
-        protected class CallerInfoWorkerHandler extends WorkerHandler {
-            public CallerInfoWorkerHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                WorkerArgs args = (WorkerArgs) msg.obj;
-                CookieWrapper cw = (CookieWrapper) args.cookie;
-
-                if (cw == null) {
-                    // Normally, this should never be the case for calls originating
-                    // from within this code.
-                    // However, if there is any code that this Handler calls (such as in
-                    // super.handleMessage) that DOES place unexpected messages on the
-                    // queue, then we need pass these messages on.
-                    Rlog.i(LOG_TAG, "Unexpected command (CookieWrapper is null): " + msg.what +
-                            " ignored by CallerInfoWorkerHandler, passing onto parent.");
-
-                    super.handleMessage(msg);
-                } else {
-
-                    Rlog.d(LOG_TAG, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 +
-                        " command: " + msg.what + " query URI: " + sanitizeUriToString(args.uri));
-
-                    switch (cw.event) {
-                        case EVENT_NEW_QUERY:
-                            //start the sql command.
-                            super.handleMessage(msg);
-                            break;
-
-                        // shortcuts to avoid query for recognized numbers.
-                        case EVENT_EMERGENCY_NUMBER:
-                        case EVENT_VOICEMAIL_NUMBER:
-
-                        case EVENT_ADD_LISTENER:
-                        case EVENT_END_OF_QUEUE:
-                            // query was already completed, so just send the reply.
-                            // passing the original token value back to the caller
-                            // on top of the event values in arg1.
-                            Message reply = args.handler.obtainMessage(msg.what);
-                            reply.obj = args;
-                            reply.arg1 = msg.arg1;
-
-                            reply.sendToTarget();
-
-                            break;
-                        case EVENT_GET_GEO_DESCRIPTION:
-                            handleGeoDescription(msg);
-                            break;
-                        default:
-                    }
-                }
-            }
-
-            private void handleGeoDescription(Message msg) {
-                WorkerArgs args = (WorkerArgs) msg.obj;
-                CookieWrapper cw = (CookieWrapper) args.cookie;
-                if (!TextUtils.isEmpty(cw.number) && cw.cookie != null && mContext != null) {
-                    final long startTimeMillis = SystemClock.elapsedRealtime();
-                    cw.geoDescription = CallerInfo.getGeoDescription(mContext, cw.number);
-                    final long duration = SystemClock.elapsedRealtime() - startTimeMillis;
-                    if (duration > 500) {
-                        if (DBG) Rlog.d(LOG_TAG, "[handleGeoDescription]" +
-                                "Spends long time to retrieve Geo description: " + duration);
-                    }
-                }
-                Message reply = args.handler.obtainMessage(msg.what);
-                reply.obj = args;
-                reply.arg1 = msg.arg1;
-                reply.sendToTarget();
-            }
-        }
-
-
-        /**
-         * Asynchronous query handler class for the contact / callerinfo object.
-         */
-        private CallerInfoAsyncQueryHandler(Context context) {
-            super(getCurrentProfileContentResolver(context));
-            mContext = context;
-        }
-
-        @Override
-        protected Handler createHandler(Looper looper) {
-            return new CallerInfoWorkerHandler(looper);
-        }
-
-        /**
-         * Overrides onQueryComplete from AsyncQueryHandler.
-         *
-         * This method takes into account the state of this class; we construct the CallerInfo
-         * object only once for each set of listeners. When the query thread has done its work
-         * and calls this method, we inform the remaining listeners in the queue, until we're
-         * out of listeners.  Once we get the message indicating that we should expect no new
-         * listeners for this CallerInfo object, we release the AsyncCursorInfo back into the
-         * pool.
-         */
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            Rlog.d(LOG_TAG, "##### onQueryComplete() #####   query complete for token: " + token);
-
-            //get the cookie and notify the listener.
-            CookieWrapper cw = (CookieWrapper) cookie;
-            if (cw == null) {
-                // Normally, this should never be the case for calls originating
-                // from within this code.
-                // However, if there is any code that calls this method, we should
-                // check the parameters to make sure they're viable.
-                Rlog.i(LOG_TAG, "Cookie is null, ignoring onQueryComplete() request.");
-                if (cursor != null) {
-                    cursor.close();
-                }
-                return;
-            }
-
-            if (cw.event == EVENT_END_OF_QUEUE) {
-                for (Runnable r : mPendingListenerCallbacks) {
-                    r.run();
-                }
-                mPendingListenerCallbacks.clear();
-
-                release();
-                if (cursor != null) {
-                    cursor.close();
-                }
-                return;
-            }
-
-            // If the cw.event == EVENT_GET_GEO_DESCRIPTION, means it would not be the 1st
-            // time entering the onQueryComplete(), mCallerInfo should not be null.
-            if (cw.event == EVENT_GET_GEO_DESCRIPTION) {
-                if (mCallerInfo != null) {
-                    mCallerInfo.geoDescription = cw.geoDescription;
-                }
-                // notify that we can clean up the queue after this.
-                CookieWrapper endMarker = new CookieWrapper();
-                endMarker.event = EVENT_END_OF_QUEUE;
-                startQuery(token, endMarker, null, null, null, null, null);
-            }
-
-            // check the token and if needed, create the callerinfo object.
-            if (mCallerInfo == null) {
-                if ((mContext == null) || (mQueryUri == null)) {
-                    throw new QueryPoolException
-                            ("Bad context or query uri, or CallerInfoAsyncQuery already released.");
-                }
-
-                // adjust the callerInfo data as needed, and only if it was set from the
-                // initial query request.
-                // Change the callerInfo number ONLY if it is an emergency number or the
-                // voicemail number, and adjust other data (including photoResource)
-                // accordingly.
-                if (cw.event == EVENT_EMERGENCY_NUMBER) {
-                    // Note we're setting the phone number here (refer to javadoc
-                    // comments at the top of CallerInfo class).
-                    mCallerInfo = new CallerInfo().markAsEmergency(mContext);
-                } else if (cw.event == EVENT_VOICEMAIL_NUMBER) {
-                    mCallerInfo = new CallerInfo().markAsVoiceMail(cw.subId);
-                } else {
-                    mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor);
-                    if (DBG) Rlog.d(LOG_TAG, "==> Got mCallerInfo: " + mCallerInfo);
-
-                    CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary(
-                            mContext, cw.number, mCallerInfo);
-                    if (newCallerInfo != mCallerInfo) {
-                        mCallerInfo = newCallerInfo;
-                        if (DBG) Rlog.d(LOG_TAG, "#####async contact look up with numeric username"
-                                + mCallerInfo);
-                    }
-
-                    // Use the number entered by the user for display.
-                    if (!TextUtils.isEmpty(cw.number)) {
-                        mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,
-                                mCallerInfo.normalizedNumber,
-                                CallerInfo.getCurrentCountryIso(mContext));
-                    }
-
-                    // This condition refer to the google default code for geo.
-                    // If the number exists in Contacts, the CallCard would never show
-                    // the geo description, so it would be unnecessary to query it.
-                    if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {
-                        if (TextUtils.isEmpty(mCallerInfo.name)) {
-                            if (DBG) Rlog.d(LOG_TAG, "start querying geo description");
-                            cw.event = EVENT_GET_GEO_DESCRIPTION;
-                            startQuery(token, cw, null, null, null, null, null);
-                            return;
-                        }
-                    }
-                }
-
-                if (DBG) Rlog.d(LOG_TAG, "constructing CallerInfo object for token: " + token);
-
-                //notify that we can clean up the queue after this.
-                CookieWrapper endMarker = new CookieWrapper();
-                endMarker.event = EVENT_END_OF_QUEUE;
-                startQuery(token, endMarker, null, null, null, null, null);
-            }
-
-            //notify the listener that the query is complete.
-            if (cw.listener != null) {
-                mPendingListenerCallbacks.add(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (DBG) Rlog.d(LOG_TAG, "notifying listener: "
-                                + cw.listener.getClass().toString() + " for token: " + token
-                                + mCallerInfo);
-                        cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
-                    }
-                });
-            } else {
-                Rlog.w(LOG_TAG, "There is no listener to notify for this query.");
-            }
-
-            if (cursor != null) {
-               cursor.close();
-            }
-        }
-    }
-
-    /**
-     * Private constructor for factory methods.
-     */
-    private CallerInfoAsyncQuery() {
-    }
-
-
-    /**
-     * Factory method to start query with a Uri query spec
-     */
-    public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef,
-            OnQueryCompleteListener listener, Object cookie) {
-
-        CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
-        c.allocate(context, contactRef);
-
-        if (DBG) Rlog.d(LOG_TAG, "starting query for URI: " + contactRef + " handler: " + c.toString());
-
-        //create cookieWrapper, start query
-        CookieWrapper cw = new CookieWrapper();
-        cw.listener = listener;
-        cw.cookie = cookie;
-        cw.event = EVENT_NEW_QUERY;
-
-        c.mHandler.startQuery(token, cw, contactRef, null, null, null, null);
-
-        return c;
-    }
-
-    /**
-     * Factory method to start the query based on a number.
-     *
-     * Note: if the number contains an "@" character we treat it
-     * as a SIP address, and look it up directly in the Data table
-     * rather than using the PhoneLookup table.
-     * TODO: But eventually we should expose two separate methods, one for
-     * numbers and one for SIP addresses, and then have
-     * PhoneUtils.startGetCallerInfo() decide which one to call based on
-     * the phone type of the incoming connection.
-     */
-    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
-            OnQueryCompleteListener listener, Object cookie) {
-
-        int subId = SubscriptionManager.getDefaultSubscriptionId();
-        return startQuery(token, context, number, listener, cookie, subId);
-    }
-
-    /**
-     * Factory method to start the query based on a number with specific subscription.
-     *
-     * Note: if the number contains an "@" character we treat it
-     * as a SIP address, and look it up directly in the Data table
-     * rather than using the PhoneLookup table.
-     * TODO: But eventually we should expose two separate methods, one for
-     * numbers and one for SIP addresses, and then have
-     * PhoneUtils.startGetCallerInfo() decide which one to call based on
-     * the phone type of the incoming connection.
-     */
-    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,
-            OnQueryCompleteListener listener, Object cookie, int subId) {
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "##### CallerInfoAsyncQuery startQuery()... #####");
-            Rlog.d(LOG_TAG, "- number: " + /*number*/ "xxxxxxx");
-            Rlog.d(LOG_TAG, "- cookie: " + cookie);
-        }
-
-        // Construct the URI object and query params, and start the query.
-
-        final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
-                .appendPath(number)
-                .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
-                        String.valueOf(PhoneNumberUtils.isUriNumber(number)))
-                .build();
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
-        }
-
-        CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
-        c.allocate(context, contactRef);
-
-        //create cookieWrapper, start query
-        CookieWrapper cw = new CookieWrapper();
-        cw.listener = listener;
-        cw.cookie = cookie;
-        cw.number = number;
-        cw.subId = subId;
-
-        // check to see if these are recognized numbers, and use shortcuts if we can.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
-            cw.event = EVENT_EMERGENCY_NUMBER;
-        } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
-            cw.event = EVENT_VOICEMAIL_NUMBER;
-        } else {
-            cw.event = EVENT_NEW_QUERY;
-        }
-
-        c.mHandler.startQuery(token,
-                              cw,  // cookie
-                              contactRef,  // uri
-                              null,  // projection
-                              null,  // selection
-                              null,  // selectionArgs
-                              null);  // orderBy
-        return c;
-    }
-
-    /**
-     * Method to add listeners to a currently running query
-     */
-    public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {
-
-        if (DBG) Rlog.d(LOG_TAG, "adding listener to query: " + sanitizeUriToString(mHandler.mQueryUri) +
-                " handler: " + mHandler.toString());
-
-        //create cookieWrapper, add query request to end of queue.
-        CookieWrapper cw = new CookieWrapper();
-        cw.listener = listener;
-        cw.cookie = cookie;
-        cw.event = EVENT_ADD_LISTENER;
-
-        mHandler.startQuery(token, cw, null, null, null, null, null);
-    }
-
-    /**
-     * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct
-     * state of context and uri.
-     */
-    private void allocate(Context context, Uri contactRef) {
-        if ((context == null) || (contactRef == null)){
-            throw new QueryPoolException("Bad context or query uri.");
-        }
-        mHandler = new CallerInfoAsyncQueryHandler(context);
-        mHandler.mQueryUri = contactRef;
-    }
-
-    /**
-     * Releases the relevant data.
-     */
-    @UnsupportedAppUsage
-    private void release() {
-        mHandler.mContext = null;
-        mHandler.mQueryUri = null;
-        mHandler.mCallerInfo = null;
-        mHandler = null;
-    }
-
-    private static String sanitizeUriToString(Uri uri) {
-        if (uri != null) {
-            String uriString = uri.toString();
-            int indexOfLastSlash = uriString.lastIndexOf('/');
-            if (indexOfLastSlash > 0) {
-                return uriString.substring(0, indexOfLastSlash) + "/xxxxxxx";
-            } else {
-                return uriString;
-            }
-        } else {
-            return "";
-        }
-    }
-}
diff --git a/telephony/java/com/android/internal/telephony/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
index 73dd822..0b73252 100644
--- a/telephony/java/com/android/internal/telephony/CbGeoUtils.java
+++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
@@ -299,7 +299,8 @@
      * @return the encoded string.
      */
     @NonNull
-    public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) {
+    public static String encodeGeometriesToString(List<Geometry> geometries) {
+        if (geometries == null || geometries.isEmpty()) return "";
         return geometries.stream()
                 .map(geometry -> encodeGeometryToString(geometry))
                 .filter(encodedStr -> !TextUtils.isEmpty(encodedStr))
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index e1113eb..668a6af 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -111,6 +111,9 @@
     public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
     public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
     public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
+    public static final int EVENT_5G_NETWORK_CHANGED = BASE + 52;
+    public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
+    public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
 
     /***** Constants *****/
 
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
deleted file mode 100644
index 34c9067..0000000
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ /dev/null
@@ -1,24 +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.internal.telephony;
-
-import android.content.ContentValues;
-
-interface IApnSourceService {
-    /** Retreive APNs. */
-    ContentValues[] getApns(int subId);
-}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4a263f0..90019ee 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -58,6 +58,8 @@
     void onRadioPowerStateChanged(in int state);
     void onCallAttributesChanged(in CallAttributes callAttributes);
     void onEmergencyNumberListChanged(in Map emergencyNumberList);
+    void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber);
+    void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
     void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
     void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9f1a2f7..866e936 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2012,6 +2012,13 @@
      */
     int getRadioHalVersion();
 
+    /**
+     * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+     * on the UICC card.
+     * @hide
+     */
+    boolean isApplicationOnUicc(int subId, int appType);
+
     boolean isModemEnabledForSlot(int slotIndex, String callingPackage);
 
     boolean isDataEnabledForApn(int apnType, int subId, String callingPackage);
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 98fee83..fe76b7c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -43,8 +43,8 @@
     void listenForSubscriber(in int subId, String pkg, IPhoneStateListener callback, int events,
             boolean notifyNow);
     @UnsupportedAppUsage
-    void notifyCallState(int state, String incomingNumber);
-    void notifyCallStateForPhoneId(in int phoneId, in int subId, int state, String incomingNumber);
+    void notifyCallStateForAllSubs(int state, String incomingNumber);
+    void notifyCallState(in int phoneId, in int subId, int state, String incomingNumber);
     void notifyServiceStateForPhoneId(in int phoneId, in int subId, in ServiceState state);
     void notifySignalStrengthForPhoneId(in int phoneId, in int subId,
             in SignalStrength signalStrength);
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index ee1a476..c9ec0f8 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -89,10 +89,6 @@
     @UnsupportedAppUsage
     public static final int PRESENTATION_PAYPHONE = 4;   // show pay phone info
 
-    // Sim activation type
-    public static final int SIM_ACTIVATION_TYPE_VOICE = 0;
-    public static final int SIM_ACTIVATION_TYPE_DATA = 1;
-
     public static final String PHONE_NAME_KEY = "phoneName";
     public static final String DATA_NETWORK_TYPE_KEY = "networkType";
     public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
deleted file mode 100644
index c912924..0000000
--- a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
- * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
- * 3GPP TS 23.041 (for GSM/UMTS).
- *
- * {@hide}
- */
-public class SmsCbCmasInfo implements Parcelable {
-
-    // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
-
-    /** Presidential-level alert (Korean Public Alert System Class 0 message). */
-    public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
-
-    /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
-    public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
-
-    /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
-    public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
-
-    /** Child abduction emergency (AMBER Alert). */
-    public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
-
-    /** CMAS test message. */
-    public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
-
-    /** CMAS exercise. */
-    public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
-
-    /** CMAS category for operator defined use. */
-    public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
-
-    /** CMAS category for warning types that are reserved for future extension. */
-    public static final int CMAS_CLASS_UNKNOWN = -1;
-
-    // CMAS alert category (in CDMA type 1 elements record).
-
-    /** CMAS alert category: Geophysical including landslide. */
-    public static final int CMAS_CATEGORY_GEO = 0x00;
-
-    /** CMAS alert category: Meteorological including flood. */
-    public static final int CMAS_CATEGORY_MET = 0x01;
-
-    /** CMAS alert category: General emergency and public safety. */
-    public static final int CMAS_CATEGORY_SAFETY = 0x02;
-
-    /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
-    public static final int CMAS_CATEGORY_SECURITY = 0x03;
-
-    /** CMAS alert category: Rescue and recovery. */
-    public static final int CMAS_CATEGORY_RESCUE = 0x04;
-
-    /** CMAS alert category: Fire suppression and rescue. */
-    public static final int CMAS_CATEGORY_FIRE = 0x05;
-
-    /** CMAS alert category: Medical and public health. */
-    public static final int CMAS_CATEGORY_HEALTH = 0x06;
-
-    /** CMAS alert category: Pollution and other environmental. */
-    public static final int CMAS_CATEGORY_ENV = 0x07;
-
-    /** CMAS alert category: Public and private transportation. */
-    public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
-
-    /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
-    public static final int CMAS_CATEGORY_INFRA = 0x09;
-
-    /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
-    public static final int CMAS_CATEGORY_CBRNE = 0x0a;
-
-    /** CMAS alert category: Other events. */
-    public static final int CMAS_CATEGORY_OTHER = 0x0b;
-
-    /**
-     * CMAS alert category is unknown. The category is only available for CDMA broadcasts
-     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
-     */
-    public static final int CMAS_CATEGORY_UNKNOWN = -1;
-
-    // CMAS response type (in CDMA type 1 elements record).
-
-    /** CMAS response type: Take shelter in place. */
-    public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
-
-    /** CMAS response type: Evacuate (Relocate). */
-    public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
-
-    /** CMAS response type: Make preparations. */
-    public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
-
-    /** CMAS response type: Execute a pre-planned activity. */
-    public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
-
-    /** CMAS response type: Attend to information sources. */
-    public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
-
-    /** CMAS response type: Avoid hazard. */
-    public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
-
-    /** CMAS response type: Evaluate the information in this message (not for public warnings). */
-    public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
-
-    /** CMAS response type: No action recommended. */
-    public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
-
-    /**
-     * CMAS response type is unknown. The response type is only available for CDMA broadcasts
-     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
-     */
-    public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
-
-    // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
-
-    /** CMAS severity type: Extraordinary threat to life or property. */
-    public static final int CMAS_SEVERITY_EXTREME = 0x0;
-
-    /** CMAS severity type: Significant threat to life or property. */
-    public static final int CMAS_SEVERITY_SEVERE = 0x1;
-
-    /**
-     * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
-     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
-     * Presidential-level alert class (Korean Public Alert System Class 0).
-     */
-    public static final int CMAS_SEVERITY_UNKNOWN = -1;
-
-    // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
-
-    /** CMAS urgency type: Responsive action should be taken immediately. */
-    public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
-
-    /** CMAS urgency type: Responsive action should be taken within the next hour. */
-    public static final int CMAS_URGENCY_EXPECTED = 0x1;
-
-    /**
-     * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
-     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
-     * Presidential-level alert class (Korean Public Alert System Class 0).
-     */
-    public static final int CMAS_URGENCY_UNKNOWN = -1;
-
-    // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
-
-    /** CMAS certainty type: Determined to have occurred or to be ongoing. */
-    public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
-
-    /** CMAS certainty type: Likely (probability > ~50%). */
-    public static final int CMAS_CERTAINTY_LIKELY = 0x1;
-
-    /**
-     * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
-     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
-     * Presidential-level alert class (Korean Public Alert System Class 0).
-     */
-    public static final int CMAS_CERTAINTY_UNKNOWN = -1;
-
-    /** CMAS message class. */
-    private final int mMessageClass;
-
-    /** CMAS category. */
-    private final int mCategory;
-
-    /** CMAS response type. */
-    private final int mResponseType;
-
-    /** CMAS severity. */
-    private final int mSeverity;
-
-    /** CMAS urgency. */
-    private final int mUrgency;
-
-    /** CMAS certainty. */
-    private final int mCertainty;
-
-    /** Create a new SmsCbCmasInfo object with the specified values. */
-    public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
-            int urgency, int certainty) {
-        mMessageClass = messageClass;
-        mCategory = category;
-        mResponseType = responseType;
-        mSeverity = severity;
-        mUrgency = urgency;
-        mCertainty = certainty;
-    }
-
-    /** Create a new SmsCbCmasInfo object from a Parcel. */
-    SmsCbCmasInfo(Parcel in) {
-        mMessageClass = in.readInt();
-        mCategory = in.readInt();
-        mResponseType = in.readInt();
-        mSeverity = in.readInt();
-        mUrgency = in.readInt();
-        mCertainty = in.readInt();
-    }
-
-    /**
-     * Flatten this object into a Parcel.
-     *
-     * @param dest  The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written (ignored).
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mMessageClass);
-        dest.writeInt(mCategory);
-        dest.writeInt(mResponseType);
-        dest.writeInt(mSeverity);
-        dest.writeInt(mUrgency);
-        dest.writeInt(mCertainty);
-    }
-
-    /**
-     * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
-     * @return one of the {@code CMAS_CLASS} values
-     */
-    public int getMessageClass() {
-        return mMessageClass;
-    }
-
-    /**
-     * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
-     * @return one of the {@code CMAS_CATEGORY} values
-     */
-    public int getCategory() {
-        return mCategory;
-    }
-
-    /**
-     * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
-     * @return one of the {@code CMAS_RESPONSE_TYPE} values
-     */
-    public int getResponseType() {
-        return mResponseType;
-    }
-
-    /**
-     * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
-     * @return one of the {@code CMAS_SEVERITY} values
-     */
-    public int getSeverity() {
-        return mSeverity;
-    }
-
-    /**
-     * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
-     * @return one of the {@code CMAS_URGENCY} values
-     */
-    public int getUrgency() {
-        return mUrgency;
-    }
-
-    /**
-     * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
-     * @return one of the {@code CMAS_CERTAINTY} values
-     */
-    public int getCertainty() {
-        return mCertainty;
-    }
-
-    @Override
-    public String toString() {
-        return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
-                + ", responseType=" + mResponseType + ", severity=" + mSeverity
-                + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
-    }
-
-    /**
-     * Describe the kinds of special objects contained in the marshalled representation.
-     * @return a bitmask indicating this Parcelable contains no special objects
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Creator for unparcelling objects. */
-    public static final Parcelable.Creator<SmsCbCmasInfo>
-            CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
-        @Override
-        public SmsCbCmasInfo createFromParcel(Parcel in) {
-            return new SmsCbCmasInfo(in);
-        }
-
-        @Override
-        public SmsCbCmasInfo[] newArray(int size) {
-            return new SmsCbCmasInfo[size];
-        }
-    };
-}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
deleted file mode 100644
index 15fbc40..0000000
--- a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.telephony.uicc.IccUtils;
-
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Arrays;
-
-/**
- * Contains information elements for a GSM or UMTS ETWS warning notification.
- * Supported values for each element are defined in 3GPP TS 23.041.
- *
- * {@hide}
- */
-public class SmsCbEtwsInfo implements Parcelable {
-
-    /** ETWS warning type for earthquake. */
-    public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
-
-    /** ETWS warning type for tsunami. */
-    public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
-
-    /** ETWS warning type for earthquake and tsunami. */
-    public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
-
-    /** ETWS warning type for test messages. */
-    public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
-
-    /** ETWS warning type for other emergency types. */
-    public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
-
-    /** Unknown ETWS warning type. */
-    public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
-
-    /** One of the ETWS warning type constants defined in this class. */
-    private final int mWarningType;
-
-    /** Whether or not to activate the emergency user alert tone and vibration. */
-    private final boolean mEmergencyUserAlert;
-
-    /** Whether or not to activate a popup alert. */
-    private final boolean mActivatePopup;
-
-    /** Whether ETWS primary message or not/ */
-    private final boolean mPrimary;
-
-    /**
-     * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
-     * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
-     * and digital signature if received. Therefore it is treated as a raw byte array and
-     * parceled with the broadcast intent if present, but the timestamp is only computed if an
-     * application asks for the individual components.
-     */
-    private final byte[] mWarningSecurityInformation;
-
-    /** Create a new SmsCbEtwsInfo object with the specified values. */
-    public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
-                boolean primary, byte[] warningSecurityInformation) {
-        mWarningType = warningType;
-        mEmergencyUserAlert = emergencyUserAlert;
-        mActivatePopup = activatePopup;
-        mPrimary = primary;
-        mWarningSecurityInformation = warningSecurityInformation;
-    }
-
-    /** Create a new SmsCbEtwsInfo object from a Parcel. */
-    SmsCbEtwsInfo(Parcel in) {
-        mWarningType = in.readInt();
-        mEmergencyUserAlert = (in.readInt() != 0);
-        mActivatePopup = (in.readInt() != 0);
-        mPrimary = (in.readInt() != 0);
-        mWarningSecurityInformation = in.createByteArray();
-    }
-
-    /**
-     * Flatten this object into a Parcel.
-     *
-     * @param dest  The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written (ignored).
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mWarningType);
-        dest.writeInt(mEmergencyUserAlert ? 1 : 0);
-        dest.writeInt(mActivatePopup ? 1 : 0);
-        dest.writeInt(mPrimary ? 1 : 0);
-        dest.writeByteArray(mWarningSecurityInformation);
-    }
-
-    /**
-     * Returns the ETWS warning type.
-     * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
-     */
-    public int getWarningType() {
-        return mWarningType;
-    }
-
-    /**
-     * Returns the ETWS emergency user alert flag.
-     * @return true to notify terminal to activate emergency user alert; false otherwise
-     */
-    public boolean isEmergencyUserAlert() {
-        return mEmergencyUserAlert;
-    }
-
-    /**
-     * Returns the ETWS activate popup flag.
-     * @return true to notify terminal to activate display popup; false otherwise
-     */
-    public boolean isPopupAlert() {
-        return mActivatePopup;
-    }
-
-    /**
-     * Returns the ETWS format flag.
-     * @return true if the message is primary message, otherwise secondary message
-     */
-    public boolean isPrimary() {
-        return mPrimary;
-    }
-
-    /**
-     * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
-     * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
-     * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
-     */
-    public long getPrimaryNotificationTimestamp() {
-        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
-            return 0;
-        }
-
-        int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
-        int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
-        int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
-        int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
-        int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
-        int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
-
-        // For the timezone, the most significant bit of the
-        // least significant nibble is the sign byte
-        // (meaning the max range of this field is 79 quarter-hours,
-        // which is more than enough)
-
-        byte tzByte = mWarningSecurityInformation[6];
-
-        // Mask out sign bit.
-        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
-
-        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
-        // timezoneOffset is in quarter hours.
-        int timeZoneOffsetSeconds = timezoneOffset * 15 * 60;
-
-        LocalDateTime localDateTime = LocalDateTime.of(
-                // We only need to support years above 2000.
-                year + 2000,
-                month /* 1-12 */,
-                day,
-                hour,
-                minute,
-                second);
-
-        long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds;
-        // Convert to milliseconds, ignore overflow.
-        return epochSeconds * 1000;
-    }
-
-    /**
-     * Returns the digital signature (GSM primary notifications only). As of Release 10,
-     * 3GPP TS 23.041 states that the UE shall ignore this value if received.
-     * @return a byte array containing a copy of the primary notification digital signature
-     */
-    public byte[] getPrimaryNotificationSignature() {
-        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
-            return null;
-        }
-        return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
-    }
-
-    @Override
-    public String toString() {
-        return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
-                + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
-    }
-
-    /**
-     * Describe the kinds of special objects contained in the marshalled representation.
-     * @return a bitmask indicating this Parcelable contains no special objects
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Creator for unparcelling objects. */
-    public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
-        @Override
-        public SmsCbEtwsInfo createFromParcel(Parcel in) {
-            return new SmsCbEtwsInfo(in);
-        }
-
-        @Override
-        public SmsCbEtwsInfo[] newArray(int size) {
-            return new SmsCbEtwsInfo[size];
-        }
-    };
-}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
deleted file mode 100644
index 6eb72a8..0000000
--- a/telephony/java/com/android/internal/telephony/SmsCbLocation.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents the location and geographical scope of a cell broadcast message.
- * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
- * geographical scope is cell wide or Location Area wide. For CDMA, the
- * broadcast geographical scope is always PLMN wide.
- *
- * @hide
- */
-public class SmsCbLocation implements Parcelable {
-
-    /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
-    private final String mPlmn;
-
-    private final int mLac;
-    private final int mCid;
-
-    /**
-     * Construct an empty location object. This is used for some test cases, and for
-     * cell broadcasts saved in older versions of the database without location info.
-     */
-    public SmsCbLocation() {
-        mPlmn = "";
-        mLac = -1;
-        mCid = -1;
-    }
-
-    /**
-     * Construct a location object for the PLMN. This class is immutable, so
-     * the same object can be reused for multiple broadcasts.
-     */
-    public SmsCbLocation(String plmn) {
-        mPlmn = plmn;
-        mLac = -1;
-        mCid = -1;
-    }
-
-    /**
-     * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
-     * the same object can be reused for multiple broadcasts.
-     */
-    public SmsCbLocation(String plmn, int lac, int cid) {
-        mPlmn = plmn;
-        mLac = lac;
-        mCid = cid;
-    }
-
-    /**
-     * Initialize the object from a Parcel.
-     */
-    public SmsCbLocation(Parcel in) {
-        mPlmn = in.readString();
-        mLac = in.readInt();
-        mCid = in.readInt();
-    }
-
-    /**
-     * Returns the MCC/MNC of the network as a String.
-     * @return the PLMN identifier (MCC+MNC) as a String
-     */
-    public String getPlmn() {
-        return mPlmn;
-    }
-
-    /**
-     * Returns the GSM location area code, or UMTS service area code.
-     * @return location area code, -1 if unknown, 0xffff max legal value
-     */
-    public int getLac() {
-        return mLac;
-    }
-
-    /**
-     * Returns the GSM or UMTS cell ID.
-     * @return gsm cell id, -1 if unknown, 0xffff max legal value
-     */
-    public int getCid() {
-        return mCid;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = mPlmn.hashCode();
-        hash = hash * 31 + mLac;
-        hash = hash * 31 + mCid;
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || !(o instanceof SmsCbLocation)) {
-            return false;
-        }
-        SmsCbLocation other = (SmsCbLocation) o;
-        return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
-    }
-
-    @Override
-    public String toString() {
-        return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
-    }
-
-    /**
-     * Test whether this location is within the location area of the specified object.
-     *
-     * @param area the location area to compare with this location
-     * @return true if this location is contained within the specified location area
-     */
-    public boolean isInLocationArea(SmsCbLocation area) {
-        if (mCid != -1 && mCid != area.mCid) {
-            return false;
-        }
-        if (mLac != -1 && mLac != area.mLac) {
-            return false;
-        }
-        return mPlmn.equals(area.mPlmn);
-    }
-
-    /**
-     * Test whether this location is within the location area of the CellLocation.
-     *
-     * @param plmn the PLMN to use for comparison
-     * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
-     * @param cid the Cell ID to compare with
-     * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
-     */
-    public boolean isInLocationArea(String plmn, int lac, int cid) {
-        if (!mPlmn.equals(plmn)) {
-            return false;
-        }
-
-        if (mLac != -1 && mLac != lac) {
-            return false;
-        }
-
-        if (mCid != -1 && mCid != cid) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Flatten this object into a Parcel.
-     *
-     * @param dest  The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written (ignored).
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mPlmn);
-        dest.writeInt(mLac);
-        dest.writeInt(mCid);
-    }
-
-    public static final Parcelable.Creator<SmsCbLocation> CREATOR
-            = new Parcelable.Creator<SmsCbLocation>() {
-        @Override
-        public SmsCbLocation createFromParcel(Parcel in) {
-            return new SmsCbLocation(in);
-        }
-
-        @Override
-        public SmsCbLocation[] newArray(int size) {
-            return new SmsCbLocation[size];
-        }
-    };
-
-    /**
-     * Describe the kinds of special objects contained in the marshalled representation.
-     * @return a bitmask indicating this Parcelable contains no special objects
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
deleted file mode 100644
index b9edb9f..0000000
--- a/telephony/java/com/android/internal/telephony/SmsCbMessage.java
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.annotation.Nullable;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.Telephony.CellBroadcasts;
-
-import com.android.internal.telephony.CbGeoUtils;
-import com.android.internal.telephony.CbGeoUtils.Geometry;
-
-import java.util.List;
-
-/**
- * Parcelable object containing a received cell broadcast message. There are four different types
- * of Cell Broadcast messages:
- *
- * <ul>
- * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
- * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
- *  roaming purposes (required to display on the idle screen in Brazil)</li>
- * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
- * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
- * </ul>
- *
- * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
- * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
- * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
- * two completely different concepts in 3GPP and CDMA.
- *
- * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
- * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
- * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
- * application should
- *
- * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
- * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
- * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
- * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
- * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
- * Service Area in UMTS). The relevant values are concatenated into a single String which will be
- * unique if the messages are not duplicates.
- *
- * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
- * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
- *
- * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
- * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
- * Only system applications such as the CellBroadcastReceiver may receive notifications for
- * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
- * interference with the immediate display of the alert message and playing of the alert sound and
- * vibration pattern, which could be caused by poorly written or malicious non-system code.
- *
- * @hide
- */
-public class SmsCbMessage implements Parcelable {
-
-    protected static final String LOG_TAG = "SMSCB";
-
-    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
-
-    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
-    public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
-
-    /** Location / service area wide geographical scope (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
-
-    /** Cell wide geographical scope (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
-
-    /** GSM or UMTS format cell broadcast. */
-    public static final int MESSAGE_FORMAT_3GPP = 1;
-
-    /** CDMA format cell broadcast. */
-    public static final int MESSAGE_FORMAT_3GPP2 = 2;
-
-    /** Normal message priority. */
-    public static final int MESSAGE_PRIORITY_NORMAL = 0;
-
-    /** Interactive message priority. */
-    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
-
-    /** Urgent message priority. */
-    public static final int MESSAGE_PRIORITY_URGENT = 2;
-
-    /** Emergency message priority. */
-    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
-
-    /** Format of this message (for interpretation of service category values). */
-    private final int mMessageFormat;
-
-    /** Geographical scope of broadcast. */
-    private final int mGeographicalScope;
-
-    /**
-     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
-     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
-     * a cell broadcast for duplicate detection.
-     */
-    private final int mSerialNumber;
-
-    /**
-     * Location identifier for this message. It consists of the current operator MCC/MNC as a
-     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
-     * message is not binary 01, the Location Area is included for comparison. If the GS is
-     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
-     */
-    private final SmsCbLocation mLocation;
-
-    /**
-     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
-     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
-     * or {@link #getCmasWarningInfo()}.
-     */
-    private final int mServiceCategory;
-
-    /** Message language, as a two-character string, e.g. "en". */
-    private final String mLanguage;
-
-    /** Message body, as a String. */
-    private final String mBody;
-
-    /** Message priority (including emergency priority). */
-    private final int mPriority;
-
-    /** ETWS warning notification information (ETWS warnings only). */
-    private final SmsCbEtwsInfo mEtwsWarningInfo;
-
-    /** CMAS warning notification information (CMAS warnings only). */
-    private final SmsCbCmasInfo mCmasWarningInfo;
-
-    /** UNIX timestamp of when the message was received. */
-    private final long mReceivedTimeMillis;
-
-    /** CMAS warning area coordinates. */
-    private final List<Geometry> mGeometries;
-
-    /**
-     * Create a new SmsCbMessage with the specified data.
-     */
-    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
-            SmsCbLocation location, int serviceCategory, String language, String body,
-            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
-
-        this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
-                body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */,
-                System.currentTimeMillis());
-    }
-
-    /**
-     * Create a new {@link SmsCbMessage} with the warning area coordinates information.
-     */
-    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
-            SmsCbLocation location, int serviceCategory, String language, String body,
-            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
-            List<Geometry> geometries, long receivedTimeMillis) {
-        mMessageFormat = messageFormat;
-        mGeographicalScope = geographicalScope;
-        mSerialNumber = serialNumber;
-        mLocation = location;
-        mServiceCategory = serviceCategory;
-        mLanguage = language;
-        mBody = body;
-        mPriority = priority;
-        mEtwsWarningInfo = etwsWarningInfo;
-        mCmasWarningInfo = cmasWarningInfo;
-        mReceivedTimeMillis = receivedTimeMillis;
-        mGeometries = geometries;
-    }
-
-    /** Create a new SmsCbMessage object from a Parcel. */
-    public SmsCbMessage(Parcel in) {
-        mMessageFormat = in.readInt();
-        mGeographicalScope = in.readInt();
-        mSerialNumber = in.readInt();
-        mLocation = new SmsCbLocation(in);
-        mServiceCategory = in.readInt();
-        mLanguage = in.readString();
-        mBody = in.readString();
-        mPriority = in.readInt();
-        int type = in.readInt();
-        switch (type) {
-            case 'E':
-                // unparcel ETWS warning information
-                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
-                mCmasWarningInfo = null;
-                break;
-
-            case 'C':
-                // unparcel CMAS warning information
-                mEtwsWarningInfo = null;
-                mCmasWarningInfo = new SmsCbCmasInfo(in);
-                break;
-
-            default:
-                mEtwsWarningInfo = null;
-                mCmasWarningInfo = null;
-        }
-        mReceivedTimeMillis = in.readLong();
-        String geoStr = in.readString();
-        mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
-    }
-
-    /**
-     * Flatten this object into a Parcel.
-     *
-     * @param dest  The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written (ignored).
-     */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mMessageFormat);
-        dest.writeInt(mGeographicalScope);
-        dest.writeInt(mSerialNumber);
-        mLocation.writeToParcel(dest, flags);
-        dest.writeInt(mServiceCategory);
-        dest.writeString(mLanguage);
-        dest.writeString(mBody);
-        dest.writeInt(mPriority);
-        if (mEtwsWarningInfo != null) {
-            // parcel ETWS warning information
-            dest.writeInt('E');
-            mEtwsWarningInfo.writeToParcel(dest, flags);
-        } else if (mCmasWarningInfo != null) {
-            // parcel CMAS warning information
-            dest.writeInt('C');
-            mCmasWarningInfo.writeToParcel(dest, flags);
-        } else {
-            // no ETWS or CMAS warning information
-            dest.writeInt('0');
-        }
-        dest.writeLong(mReceivedTimeMillis);
-        dest.writeString(
-                mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
-    }
-
-    public static final Parcelable.Creator<SmsCbMessage> CREATOR
-            = new Parcelable.Creator<SmsCbMessage>() {
-        @Override
-        public SmsCbMessage createFromParcel(Parcel in) {
-            return new SmsCbMessage(in);
-        }
-
-        @Override
-        public SmsCbMessage[] newArray(int size) {
-            return new SmsCbMessage[size];
-        }
-    };
-
-    /**
-     * Return the geographical scope of this message (GSM/UMTS only).
-     *
-     * @return Geographical scope
-     */
-    public int getGeographicalScope() {
-        return mGeographicalScope;
-    }
-
-    /**
-     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
-     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
-     * the location code uniquely identify a cell broadcast for duplicate detection.
-     *
-     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
-     */
-    public int getSerialNumber() {
-        return mSerialNumber;
-    }
-
-    /**
-     * Return the location identifier for this message, consisting of the MCC/MNC as a
-     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
-     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
-     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
-     * if the location is included within another location area or within a PLMN and CellLocation.
-     *
-     * @return the geographical location code for duplicate message detection
-     */
-    public SmsCbLocation getLocation() {
-        return mLocation;
-    }
-
-    /**
-     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
-     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
-     * provided by the category is available via {@link #getEtwsWarningInfo()} or
-     * {@link #getCmasWarningInfo()} in a radio technology independent format.
-     *
-     * @return the radio technology specific service category
-     */
-    public int getServiceCategory() {
-        return mServiceCategory;
-    }
-
-    /**
-     * Get the ISO-639-1 language code for this message, or null if unspecified
-     *
-     * @return Language code
-     */
-    public String getLanguageCode() {
-        return mLanguage;
-    }
-
-    /**
-     * Get the body of this message, or null if no body available
-     *
-     * @return Body, or null
-     */
-    public String getMessageBody() {
-        return mBody;
-    }
-
-    /**
-     * Get the warning area coordinates information represent by polygons and circles.
-     * @return a list of geometries, {@link Nullable} means there is no coordinate information
-     * associated to this message.
-     */
-    @Nullable
-    public List<Geometry> getGeometries() {
-        return mGeometries;
-    }
-
-    /**
-     * Get the time when this message was received.
-     * @return the time in millisecond
-     */
-    public long getReceivedTime() {
-        return mReceivedTimeMillis;
-    }
-
-    /**
-     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
-     * @return an integer representing 3GPP or 3GPP2 message format
-     */
-    public int getMessageFormat() {
-        return mMessageFormat;
-    }
-
-    /**
-     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
-     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
-     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
-     * @return an integer representing the message priority
-     */
-    public int getMessagePriority() {
-        return mPriority;
-    }
-
-    /**
-     * If this is an ETWS warning notification then this method will return an object containing
-     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
-     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
-     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
-     * ETWS primary notification timestamp and digital signature if received.
-     *
-     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
-     */
-    public SmsCbEtwsInfo getEtwsWarningInfo() {
-        return mEtwsWarningInfo;
-    }
-
-    /**
-     * If this is a CMAS warning notification then this method will return an object containing
-     * the CMAS message class, category, response type, severity, urgency and certainty.
-     * The message class is always present. Severity, urgency and certainty are present for CDMA
-     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
-     * except for the Presidential-level alert category. Category and response type are only
-     * available for CDMA notifications containing a type 1 elements record.
-     *
-     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
-     */
-    public SmsCbCmasInfo getCmasWarningInfo() {
-        return mCmasWarningInfo;
-    }
-
-    /**
-     * Return whether this message is an emergency (PWS) message type.
-     * @return true if the message is a public warning notification; false otherwise
-     */
-    public boolean isEmergencyMessage() {
-        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
-    }
-
-    /**
-     * Return whether this message is an ETWS warning alert.
-     * @return true if the message is an ETWS warning notification; false otherwise
-     */
-    public boolean isEtwsMessage() {
-        return mEtwsWarningInfo != null;
-    }
-
-    /**
-     * Return whether this message is a CMAS warning alert.
-     * @return true if the message is a CMAS warning notification; false otherwise
-     */
-    public boolean isCmasMessage() {
-        return mCmasWarningInfo != null;
-    }
-
-    @Override
-    public String toString() {
-        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
-                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
-                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
-                + ", priority=" + mPriority
-                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
-                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
-                + ", geo=" + (mGeometries != null
-                ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
-                + '}';
-    }
-
-    /**
-     * Describe the kinds of special objects contained in the marshalled representation.
-     * @return a bitmask indicating this Parcelable contains no special objects
-     */
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /**
-     * @return the {@link ContentValues} instance that includes the cell broadcast data.
-     */
-    public ContentValues getContentValues() {
-        ContentValues cv = new ContentValues(16);
-        cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
-        if (mLocation.getPlmn() != null) {
-            cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
-        }
-        if (mLocation.getLac() != -1) {
-            cv.put(CellBroadcasts.LAC, mLocation.getLac());
-        }
-        if (mLocation.getCid() != -1) {
-            cv.put(CellBroadcasts.CID, mLocation.getCid());
-        }
-        cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber());
-        cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory());
-        cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode());
-        cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody());
-        cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat());
-        cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority());
-
-        SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
-        if (etwsInfo != null) {
-            cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
-        }
-
-        SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
-        if (cmasInfo != null) {
-            cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
-            cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
-            cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
-            cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
-            cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
-            cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
-        }
-
-        cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis);
-
-        if (mGeometries != null) {
-            cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries));
-        } else {
-            cv.put(CellBroadcasts.GEOMETRIES, (String) null);
-        }
-
-        return cv;
-    }
-
-    /**
-     * Create a {@link SmsCbMessage} instance from a row in the cell broadcast database.
-     * @param cursor an open SQLite cursor pointing to the row to read
-     * @return a {@link SmsCbMessage} instance.
-     * @throws IllegalArgumentException if one of the required columns is missing
-     */
-    public static SmsCbMessage createFromCursor(Cursor cursor) {
-        int geoScope = cursor.getInt(
-                cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
-        int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
-        int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY));
-        String language = cursor.getString(
-                cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE));
-        String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY));
-        int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
-        int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
-
-        String plmn;
-        int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
-        if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
-            plmn = cursor.getString(plmnColumn);
-        } else {
-            plmn = null;
-        }
-
-        int lac;
-        int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC);
-        if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
-            lac = cursor.getInt(lacColumn);
-        } else {
-            lac = -1;
-        }
-
-        int cid;
-        int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID);
-        if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
-            cid = cursor.getInt(cidColumn);
-        } else {
-            cid = -1;
-        }
-
-        SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
-
-        SmsCbEtwsInfo etwsInfo;
-        int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
-        if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
-            int warningType = cursor.getInt(etwsWarningTypeColumn);
-            etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
-        } else {
-            etwsInfo = null;
-        }
-
-        SmsCbCmasInfo cmasInfo = null;
-        int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS);
-        if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
-            int messageClass = cursor.getInt(cmasMessageClassColumn);
-
-            int cmasCategory;
-            int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY);
-            if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
-                cmasCategory = cursor.getInt(cmasCategoryColumn);
-            } else {
-                cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
-            }
-
-            int responseType;
-            int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE);
-            if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
-                responseType = cursor.getInt(cmasResponseTypeColumn);
-            } else {
-                responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
-            }
-
-            int severity;
-            int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY);
-            if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
-                severity = cursor.getInt(cmasSeverityColumn);
-            } else {
-                severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
-            }
-
-            int urgency;
-            int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY);
-            if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
-                urgency = cursor.getInt(cmasUrgencyColumn);
-            } else {
-                urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
-            }
-
-            int certainty;
-            int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY);
-            if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
-                certainty = cursor.getInt(cmasCertaintyColumn);
-            } else {
-                certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
-            }
-
-            cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
-                    urgency, certainty);
-        }
-
-        String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES));
-        List<Geometry> geometries =
-                geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
-
-        long receivedTimeSec = cursor.getLong(
-                cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
-
-        return new SmsCbMessage(format, geoScope, serialNum, location, category,
-                language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec);
-    }
-
-    /**
-     * @return {@code True} if this message needs geo-fencing check.
-     */
-    public boolean needGeoFencingCheck() {
-        return mGeometries != null;
-    }
-}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index ef485071..4e42c20 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -234,4 +234,11 @@
     String DISPLAY_OPPORTUNISTIC_SUBSCRIPTION_CARRIER_TEXT_PROPERTY_NAME =
             "persist.radio.display_opportunistic_carrier";
 
+    /**
+     * How many logical modems can be active simultaneously. For example, if a device is dual-SIM
+     * capable but currently only one SIM slot and one logical modem is active, this value is still
+     * two.
+     * Type: int
+     */
+    static final String PROPERTY_MAX_ACTIVE_MODEMS = "ro.telephony.max.active.modems";
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 6b3126d..d892e55 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -696,6 +696,89 @@
     }
 
     /**
+     * Pre-processes an SMS WAP for Teleservice Id 0xFDEA(65002).
+     *
+     * It requires an additional header parsing to extract new Message Identifier and new User Data
+     * from WDP SMS User Data.
+     *
+     * - WDP SMS User Data Subparameter =
+     *   |User Data SUBPARAMETER_ID ~ NUM_FIELDS| + |CHARi| + |RESERVED|
+     *
+     * - WDP SMS User Data Subparameter CHARi =
+     *   |New Message Identifier Subparameter(HEADER_IND = 0)| +
+     *   |New User Data Subparameter(MSG_ENCODING = ENCODING_OCTET)|
+     *
+     * @return true if preprocessing is successful, false otherwise.
+     */
+    public boolean preprocessCdmaFdeaWap() {
+        try {
+            BitwiseInputStream inStream = new BitwiseInputStream(mUserData);
+
+            // Message Identifier SUBPARAMETER_ID(0x00)
+            if (inStream.read(8) != 0x00) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAMETER_ID");
+                return false;
+            }
+
+            // Message Identifier SUBPARAM_LEN(0x03)
+            if (inStream.read(8) != 0x03) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier SUBPARAM_LEN");
+                return false;
+            }
+
+            // Message Identifier MESSAGE_TYPE
+            mBearerData.messageType = inStream.read(4);
+
+            // Message Identifier MESSAGE_ID
+            int msgId = inStream.read(8) << 8;
+            msgId |= inStream.read(8);
+            mBearerData.messageId = msgId;
+            mMessageRef = msgId;
+
+            // Message Identifier HEADER_IND
+            mBearerData.hasUserDataHeader = (inStream.read(1) == 1);
+            if (mBearerData.hasUserDataHeader) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header Message Identifier HEADER_IND");
+                return false;
+            }
+
+            // Message Identifier RESERVED
+            inStream.skip(3);
+
+            // User Data SUBPARAMETER_ID(0x01)
+            if (inStream.read(8) != 0x01) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data SUBPARAMETER_ID");
+                return false;
+            }
+
+            // User Data SUBPARAM_LEN
+            int userDataLen = inStream.read(8) * 8;
+
+            // User Data MSG_ENCODING
+            mBearerData.userData.msgEncoding = inStream.read(5);
+            int consumedBits = 5;
+            if (mBearerData.userData.msgEncoding != UserData.ENCODING_OCTET) {
+                Rlog.e(LOG_TAG, "Invalid FDEA WDP Header User Data MSG_ENCODING");
+                return false;
+            }
+
+            // User Data NUM_FIELDS
+            mBearerData.userData.numFields = inStream.read(8);
+            consumedBits += 8;
+
+            int remainingBits = userDataLen - consumedBits;
+            int dataBits = mBearerData.userData.numFields * 8;
+            dataBits = dataBits < remainingBits ? dataBits : remainingBits;
+            mBearerData.userData.payload = inStream.readByteArray(dataBits);
+            mUserData = mBearerData.userData.payload;
+            return true;
+        } catch (BitwiseInputStream.AccessException ex) {
+            Rlog.e(LOG_TAG, "Fail to preprocess FDEA WAP: " + ex);
+        }
+        return false;
+    }
+
+    /**
      * Parses a SMS message from its BearerData stream.
      */
     @UnsupportedAppUsage
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 2181bc4..d9be548 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -781,13 +781,13 @@
                                       " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
         }
 
-        /*
-         * TODO(cleanup): figure out what the right answer is WRT paddingBits field
-         *
-         *   userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
-         *   userData.paddingBits = 0; // XXX this seems better, but why?
-         *
-         */
+        if (bData.userData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+            bData.userData.paddingBits =
+                    (bData.userData.payload.length * 8) - (bData.userData.numFields * 7);
+        } else {
+            bData.userData.paddingBits = 0;
+        }
+
         int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
         int paramBits = dataBits + 13;
         if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 6af174c..3440c65 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -39,6 +39,9 @@
     static public final int TELESERVICE_WEMT              = 0x1005;
     static public final int TELESERVICE_SCPT              = 0x1006;
 
+    /** Carriers specific Teleservice IDs. */
+    public static final int TELESERVICE_FDEA_WAP = 0xFDEA; // 65002
+
     /**
      * The following are defined as extensions to the standard teleservices
      */
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index dca4e6b..6eea118 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -104,7 +104,7 @@
                     header.getSerialNumber(), location, header.getServiceCategory(), null,
                     getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
                     SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
-                    header.getCmasInfo(), null /* geometries */, receivedTimeMillis);
+                    header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis);
         } else if (header.isUmtsFormat()) {
             // UMTS format has only 1 PDU
             byte[] pdu = pdus[0];
@@ -120,9 +120,13 @@
 
             // Has Warning Area Coordinates information
             List<Geometry> geometries = null;
+            int maximumWaitingTimeSec = 255;
             if (pdu.length > wacDataOffset) {
                 try {
-                    geometries = parseWarningAreaCoordinates(pdu, wacDataOffset);
+                    Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
+                            wacDataOffset);
+                    maximumWaitingTimeSec = wac.first;
+                    geometries = wac.second;
                 } catch (Exception ex) {
                     // Catch the exception here, the message will be considered as having no WAC
                     // information which means the message will be broadcasted directly.
@@ -133,7 +137,8 @@
             return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, body, priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis);
+                    header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
+                    receivedTimeMillis);
         } else {
             String language = null;
             StringBuilder sb = new StringBuilder();
@@ -148,7 +153,7 @@
             return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, sb.toString(), priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */,
+                    header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
                     receivedTimeMillis);
         }
     }
@@ -197,7 +202,17 @@
         }
     }
 
-    private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) {
+    /**
+     * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
+     *
+     * @param pdu Warning Area Coordinates TLV.
+     * @param wacOffset the offset of Warning Area Coordinates TLV.
+     * @return a pair with the first element is maximum wait time and the second is the broadcast
+     * area. The default value of the maximum wait time is 255 which means use the device default
+     * value.
+     */
+    private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
+            byte[] pdu, int wacOffset) {
         // little-endian
         int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
         int offset = wacOffset + 2;
@@ -209,6 +224,8 @@
 
         BitStreamReader bitReader = new BitStreamReader(pdu, offset);
 
+        int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
+
         List<Geometry> geo = new ArrayList<>();
         int remainedBytes = wacDataLength;
         while (remainedBytes > 0) {
@@ -220,8 +237,7 @@
 
             switch (type) {
                 case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
-                    // TODO: handle the maximum wait time in cell broadcast provider.
-                    int maximumWaitTimeSec = bitReader.read(8);
+                    maximumWaitTimeSec = bitReader.read(8);
                     break;
                 case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
                     List<LatLng> latLngs = new ArrayList<>();
@@ -247,7 +263,7 @@
                     throw new IllegalArgumentException("Unsupported geoType = " + type);
             }
         }
-        return geo;
+        return new Pair(maximumWaitTimeSec, geo);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index da32c8c..28b331b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -1496,7 +1496,7 @@
      *
      * @return true if this is a USIM data download message; false otherwise
      */
-    boolean isUsimDataDownload() {
+    public boolean isUsimDataDownload() {
         return messageClass == MessageClass.CLASS_2 &&
                 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
     }
diff --git a/telephony/java/com/google/android/mms/ContentType.java b/telephony/java/com/google/android/mms/ContentType.java
new file mode 100644
index 0000000..12e4b7e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/ContentType.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+
+public class ContentType {
+    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
+    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+    // Notification.ind, Delivery.ind).
+    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
+    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
+    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+    public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+    public static final String TEXT_PLAIN        = "text/plain";
+    public static final String TEXT_HTML         = "text/html";
+    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
+    public static final String TEXT_VCARD        = "text/x-vCard";
+
+    public static final String IMAGE_UNSPECIFIED = "image/*";
+    public static final String IMAGE_JPEG        = "image/jpeg";
+    public static final String IMAGE_JPG         = "image/jpg";
+    public static final String IMAGE_GIF         = "image/gif";
+    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
+    public static final String IMAGE_PNG         = "image/png";
+    public static final String IMAGE_X_MS_BMP    = "image/x-ms-bmp";
+
+    public static final String AUDIO_UNSPECIFIED = "audio/*";
+    public static final String AUDIO_AAC         = "audio/aac";
+    public static final String AUDIO_AMR         = "audio/amr";
+    public static final String AUDIO_IMELODY     = "audio/imelody";
+    public static final String AUDIO_MID         = "audio/mid";
+    public static final String AUDIO_MIDI        = "audio/midi";
+    public static final String AUDIO_MP3         = "audio/mp3";
+    public static final String AUDIO_MPEG3       = "audio/mpeg3";
+    public static final String AUDIO_MPEG        = "audio/mpeg";
+    public static final String AUDIO_MPG         = "audio/mpg";
+    public static final String AUDIO_MP4         = "audio/mp4";
+    public static final String AUDIO_X_MID       = "audio/x-mid";
+    public static final String AUDIO_X_MIDI      = "audio/x-midi";
+    public static final String AUDIO_X_MP3       = "audio/x-mp3";
+    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
+    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
+    public static final String AUDIO_X_MPG       = "audio/x-mpg";
+    public static final String AUDIO_3GPP        = "audio/3gpp";
+    public static final String AUDIO_X_WAV       = "audio/x-wav";
+    public static final String AUDIO_OGG         = "application/ogg";
+    public static final String AUDIO_OGG2        = "audio/ogg";
+
+    public static final String VIDEO_UNSPECIFIED = "video/*";
+    public static final String VIDEO_3GPP        = "video/3gpp";
+    public static final String VIDEO_3G2         = "video/3gpp2";
+    public static final String VIDEO_H263        = "video/h263";
+    public static final String VIDEO_MP4         = "video/mp4";
+
+    public static final String APP_SMIL          = "application/smil";
+    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
+    public static final String APP_XHTML         = "application/xhtml+xml";
+
+    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
+    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
+
+    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+    static {
+        sSupportedContentTypes.add(TEXT_PLAIN);
+        sSupportedContentTypes.add(TEXT_HTML);
+        sSupportedContentTypes.add(TEXT_VCALENDAR);
+        sSupportedContentTypes.add(TEXT_VCARD);
+
+        sSupportedContentTypes.add(IMAGE_JPEG);
+        sSupportedContentTypes.add(IMAGE_GIF);
+        sSupportedContentTypes.add(IMAGE_WBMP);
+        sSupportedContentTypes.add(IMAGE_PNG);
+        sSupportedContentTypes.add(IMAGE_JPG);
+        sSupportedContentTypes.add(IMAGE_X_MS_BMP);
+        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+        sSupportedContentTypes.add(AUDIO_AAC);
+        sSupportedContentTypes.add(AUDIO_AMR);
+        sSupportedContentTypes.add(AUDIO_IMELODY);
+        sSupportedContentTypes.add(AUDIO_MID);
+        sSupportedContentTypes.add(AUDIO_MIDI);
+        sSupportedContentTypes.add(AUDIO_MP3);
+        sSupportedContentTypes.add(AUDIO_MP4);
+        sSupportedContentTypes.add(AUDIO_MPEG3);
+        sSupportedContentTypes.add(AUDIO_MPEG);
+        sSupportedContentTypes.add(AUDIO_MPG);
+        sSupportedContentTypes.add(AUDIO_X_MID);
+        sSupportedContentTypes.add(AUDIO_X_MIDI);
+        sSupportedContentTypes.add(AUDIO_X_MP3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG);
+        sSupportedContentTypes.add(AUDIO_X_MPG);
+        sSupportedContentTypes.add(AUDIO_X_WAV);
+        sSupportedContentTypes.add(AUDIO_3GPP);
+        sSupportedContentTypes.add(AUDIO_OGG);
+        sSupportedContentTypes.add(AUDIO_OGG2);
+
+        sSupportedContentTypes.add(VIDEO_3GPP);
+        sSupportedContentTypes.add(VIDEO_3G2);
+        sSupportedContentTypes.add(VIDEO_H263);
+        sSupportedContentTypes.add(VIDEO_MP4);
+
+        sSupportedContentTypes.add(APP_SMIL);
+        sSupportedContentTypes.add(APP_WAP_XHTML);
+        sSupportedContentTypes.add(APP_XHTML);
+
+        sSupportedContentTypes.add(APP_DRM_CONTENT);
+        sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+        // add supported image types
+        sSupportedImageTypes.add(IMAGE_JPEG);
+        sSupportedImageTypes.add(IMAGE_GIF);
+        sSupportedImageTypes.add(IMAGE_WBMP);
+        sSupportedImageTypes.add(IMAGE_PNG);
+        sSupportedImageTypes.add(IMAGE_JPG);
+        sSupportedImageTypes.add(IMAGE_X_MS_BMP);
+
+        // add supported audio types
+        sSupportedAudioTypes.add(AUDIO_AAC);
+        sSupportedAudioTypes.add(AUDIO_AMR);
+        sSupportedAudioTypes.add(AUDIO_IMELODY);
+        sSupportedAudioTypes.add(AUDIO_MID);
+        sSupportedAudioTypes.add(AUDIO_MIDI);
+        sSupportedAudioTypes.add(AUDIO_MP3);
+        sSupportedAudioTypes.add(AUDIO_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_MPEG);
+        sSupportedAudioTypes.add(AUDIO_MPG);
+        sSupportedAudioTypes.add(AUDIO_MP4);
+        sSupportedAudioTypes.add(AUDIO_X_MID);
+        sSupportedAudioTypes.add(AUDIO_X_MIDI);
+        sSupportedAudioTypes.add(AUDIO_X_MP3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG);
+        sSupportedAudioTypes.add(AUDIO_X_MPG);
+        sSupportedAudioTypes.add(AUDIO_X_WAV);
+        sSupportedAudioTypes.add(AUDIO_3GPP);
+        sSupportedAudioTypes.add(AUDIO_OGG);
+        sSupportedAudioTypes.add(AUDIO_OGG2);
+
+        // add supported video types
+        sSupportedVideoTypes.add(VIDEO_3GPP);
+        sSupportedVideoTypes.add(VIDEO_3G2);
+        sSupportedVideoTypes.add(VIDEO_H263);
+        sSupportedVideoTypes.add(VIDEO_MP4);
+    }
+
+    // This class should never be instantiated.
+    private ContentType() {
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedType(String contentType) {
+        return (null != contentType) && sSupportedContentTypes.contains(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedImageType(String contentType) {
+        return isImageType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedAudioType(String contentType) {
+        return isAudioType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedVideoType(String contentType) {
+        return isVideoType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isTextType(String contentType) {
+        return (null != contentType) && contentType.startsWith("text/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isImageType(String contentType) {
+        return (null != contentType) && contentType.startsWith("image/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isAudioType(String contentType) {
+        return (null != contentType) && contentType.startsWith("audio/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isVideoType(String contentType) {
+        return (null != contentType) && contentType.startsWith("video/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isDrmType(String contentType) {
+        return (null != contentType)
+                && (contentType.equals(APP_DRM_CONTENT)
+                        || contentType.equals(APP_DRM_MESSAGE));
+    }
+
+    public static boolean isUnspecified(String contentType) {
+        return (null != contentType) && contentType.endsWith("*");
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getImageTypes() {
+        return (ArrayList<String>) sSupportedImageTypes.clone();
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getAudioTypes() {
+        return (ArrayList<String>) sSupportedAudioTypes.clone();
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getVideoTypes() {
+        return (ArrayList<String>) sSupportedVideoTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getSupportedTypes() {
+        return (ArrayList<String>) sSupportedContentTypes.clone();
+    }
+}
diff --git a/telephony/java/com/google/android/mms/InvalidHeaderValueException.java b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
new file mode 100644
index 0000000..2836c30
--- /dev/null
+++ b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+    private static final long serialVersionUID = -2053384496042052262L;
+
+    /**
+     * Constructs an InvalidHeaderValueException with no detailed message.
+     */
+    public InvalidHeaderValueException() {
+        super();
+    }
+
+    /**
+     * Constructs an InvalidHeaderValueException with the specified detailed message.
+     *
+     * @param message the detailed message.
+     */
+    @UnsupportedAppUsage
+    public InvalidHeaderValueException(String message) {
+        super(message);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/MmsException.java b/telephony/java/com/google/android/mms/MmsException.java
new file mode 100644
index 0000000..5be33ed
--- /dev/null
+++ b/telephony/java/com/google/android/mms/MmsException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+    private static final long serialVersionUID = -7323249827281485390L;
+
+    /**
+     * Creates a new MmsException.
+     */
+    @UnsupportedAppUsage
+    public MmsException() {
+        super();
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message.
+     *
+     * @param message the detail message.
+     */
+    @UnsupportedAppUsage
+    public MmsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new MmsException with the specified cause.
+     *
+     * @param cause the cause.
+     */
+    @UnsupportedAppUsage
+    public MmsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message and cause.
+     *
+     * @param message the detail message.
+     * @param cause the cause.
+     */
+    @UnsupportedAppUsage
+    public MmsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/package.html b/telephony/java/com/google/android/mms/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..ae447d7
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-Acknowledge.ind pdu.
+     *
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     */
+    @UnsupportedAppUsage
+    public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+            throws InvalidHeaderValueException {
+        super();
+
+        setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    AcknowledgeInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/Base64.java b/telephony/java/com/google/android/mms/pdu/Base64.java
new file mode 100644
index 0000000..483fa7f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/Base64.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class Base64 {
+    /**
+     * Used to get the number of Quadruples.
+     */
+    static final int FOURBYTE = 4;
+
+    /**
+     * Byte used to pad output.
+     */
+    static final byte PAD = (byte) '=';
+
+    /**
+     * The base length.
+     */
+    static final int BASELENGTH = 255;
+
+    // Create arrays to hold the base64 characters
+    private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+    // Populating the character arrays
+    static {
+        for (int i = 0; i < BASELENGTH; i++) {
+            base64Alphabet[i] = (byte) -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param base64Data Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    @UnsupportedAppUsage
+    public static byte[] decodeBase64(byte[] base64Data) {
+        // RFC 2045 requires that we discard ALL non-Base64 characters
+        base64Data = discardNonBase64(base64Data);
+
+        // handle the edge case, so we don't have to worry about it later
+        if (base64Data.length == 0) {
+            return new byte[0];
+        }
+
+        int numberQuadruple = base64Data.length / FOURBYTE;
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        {
+            // this sizes the output array properly - rlw
+            int lastData = base64Data.length;
+            // ignore the '=' padding
+            while (base64Data[lastData - 1] == PAD) {
+                if (--lastData == 0) {
+                    return new byte[0];
+                }
+            }
+            decodedData = new byte[lastData - numberQuadruple];
+        }
+
+        for (int i = 0; i < numberQuadruple; i++) {
+            dataIndex = i * 4;
+            marker0 = base64Data[dataIndex + 2];
+            marker1 = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+            if (marker0 != PAD && marker1 != PAD) {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[marker0];
+                b4 = base64Alphabet[marker1];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+            } else if (marker0 == PAD) {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+            } else if (marker1 == PAD) {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[marker0];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            }
+            encodedIndex += 3;
+        }
+        return decodedData;
+    }
+
+    /**
+     * Check octect whether it is a base64 encoding.
+     *
+     * @param octect to be checked byte
+     * @return ture if it is base64 encoding, false otherwise.
+     */
+    private static boolean isBase64(byte octect) {
+        if (octect == PAD) {
+            return true;
+        } else if (base64Alphabet[octect] == -1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Discards any characters outside of the base64 alphabet, per
+     * the requirements on page 25 of RFC 2045 - "Any characters
+     * outside of the base64 alphabet are to be ignored in base64
+     * encoded data."
+     *
+     * @param data The base-64 encoded data to groom
+     * @return The data, less non-base64 characters (see RFC 2045).
+     */
+    static byte[] discardNonBase64(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (int i = 0; i < data.length; i++) {
+            if (isBase64(data[i])) {
+                groomedData[bytesCopied++] = data[i];
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/CharacterSets.java b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
new file mode 100644
index 0000000..27da35e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+    /**
+     * IANA assigned MIB enum numbers.
+     *
+     * From wap-230-wsp-20010705-a.pdf
+     * Any-charset = <Octet 128>
+     * Equivalent to the special RFC2616 charset value "*"
+     */
+    public static final int ANY_CHARSET = 0x00;
+    public static final int US_ASCII    = 0x03;
+    public static final int ISO_8859_1  = 0x04;
+    public static final int ISO_8859_2  = 0x05;
+    public static final int ISO_8859_3  = 0x06;
+    public static final int ISO_8859_4  = 0x07;
+    public static final int ISO_8859_5  = 0x08;
+    public static final int ISO_8859_6  = 0x09;
+    public static final int ISO_8859_7  = 0x0A;
+    public static final int ISO_8859_8  = 0x0B;
+    public static final int ISO_8859_9  = 0x0C;
+    public static final int SHIFT_JIS   = 0x11;
+    public static final int UTF_8       = 0x6A;
+    public static final int BIG5        = 0x07EA;
+    public static final int UCS2        = 0x03E8;
+    public static final int UTF_16      = 0x03F7;
+
+    /**
+     * If the encoding of given data is unsupported, use UTF_8 to decode it.
+     */
+    public static final int DEFAULT_CHARSET = UTF_8;
+
+    /**
+     * Array of MIB enum numbers.
+     */
+    private static final int[] MIBENUM_NUMBERS = {
+        ANY_CHARSET,
+        US_ASCII,
+        ISO_8859_1,
+        ISO_8859_2,
+        ISO_8859_3,
+        ISO_8859_4,
+        ISO_8859_5,
+        ISO_8859_6,
+        ISO_8859_7,
+        ISO_8859_8,
+        ISO_8859_9,
+        SHIFT_JIS,
+        UTF_8,
+        BIG5,
+        UCS2,
+        UTF_16,
+    };
+
+    /**
+     * The Well-known-charset Mime name.
+     */
+    public static final String MIMENAME_ANY_CHARSET = "*";
+    public static final String MIMENAME_US_ASCII    = "us-ascii";
+    public static final String MIMENAME_ISO_8859_1  = "iso-8859-1";
+    public static final String MIMENAME_ISO_8859_2  = "iso-8859-2";
+    public static final String MIMENAME_ISO_8859_3  = "iso-8859-3";
+    public static final String MIMENAME_ISO_8859_4  = "iso-8859-4";
+    public static final String MIMENAME_ISO_8859_5  = "iso-8859-5";
+    public static final String MIMENAME_ISO_8859_6  = "iso-8859-6";
+    public static final String MIMENAME_ISO_8859_7  = "iso-8859-7";
+    public static final String MIMENAME_ISO_8859_8  = "iso-8859-8";
+    public static final String MIMENAME_ISO_8859_9  = "iso-8859-9";
+    public static final String MIMENAME_SHIFT_JIS   = "shift_JIS";
+    public static final String MIMENAME_UTF_8       = "utf-8";
+    public static final String MIMENAME_BIG5        = "big5";
+    public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
+    public static final String MIMENAME_UTF_16      = "utf-16";
+
+    public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+    /**
+     * Array of the names of character sets.
+     */
+    private static final String[] MIME_NAMES = {
+        MIMENAME_ANY_CHARSET,
+        MIMENAME_US_ASCII,
+        MIMENAME_ISO_8859_1,
+        MIMENAME_ISO_8859_2,
+        MIMENAME_ISO_8859_3,
+        MIMENAME_ISO_8859_4,
+        MIMENAME_ISO_8859_5,
+        MIMENAME_ISO_8859_6,
+        MIMENAME_ISO_8859_7,
+        MIMENAME_ISO_8859_8,
+        MIMENAME_ISO_8859_9,
+        MIMENAME_SHIFT_JIS,
+        MIMENAME_UTF_8,
+        MIMENAME_BIG5,
+        MIMENAME_UCS2,
+        MIMENAME_UTF_16,
+    };
+
+    private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+    private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+    static {
+        // Create the HashMaps.
+        MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+        NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+        assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+        int count = MIBENUM_NUMBERS.length - 1;
+        for(int i = 0; i <= count; i++) {
+            MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+            NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+        }
+    }
+
+    private CharacterSets() {} // Non-instantiatable
+
+    /**
+     * Map an MIBEnum number to the name of the charset which this number
+     * is assigned to by IANA.
+     *
+     * @param mibEnumValue An IANA assigned MIBEnum number.
+     * @return The name string of the charset.
+     * @throws UnsupportedEncodingException
+     */
+    @UnsupportedAppUsage
+    public static String getMimeName(int mibEnumValue)
+            throws UnsupportedEncodingException {
+        String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+        if (name == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return name;
+    }
+
+    /**
+     * Map a well-known charset name to its assigned MIBEnum number.
+     *
+     * @param mimeName The charset name.
+     * @return The MIBEnum number assigned by IANA for this charset.
+     * @throws UnsupportedEncodingException
+     */
+    @UnsupportedAppUsage
+    public static int getMibEnumValue(String mimeName)
+            throws UnsupportedEncodingException {
+        if(null == mimeName) {
+            return -1;
+        }
+
+        Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+        if (mibEnumValue == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return mibEnumValue;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/DeliveryInd.java b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..7093ac6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public DeliveryInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    DeliveryInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value, should not be null
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get Status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Set Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public EncodedStringValue getStatusText() {return null;}
+     *     public void setStatusText(EncodedStringValue value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..4166275
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+    private static final String TAG = "EncodedStringValue";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    /**
+     * The Char-set value.
+     */
+    private int mCharacterSet;
+
+    /**
+     * The Text-string value.
+     */
+    private byte[] mData;
+
+    /**
+     * Constructor.
+     *
+     * @param charset the Char-set value
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue(int charset, byte[] data) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        if(null == data) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mCharacterSet = charset;
+        mData = new byte[data.length];
+        System.arraycopy(data, 0, mData, 0, data.length);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue(byte[] data) {
+        this(CharacterSets.DEFAULT_CHARSET, data);
+    }
+
+    @UnsupportedAppUsage
+    public EncodedStringValue(String data) {
+        try {
+            mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+            mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Default encoding must be supported.", e);
+        }
+    }
+
+    /**
+     * Get Char-set value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getCharacterSet() {
+        return mCharacterSet;
+    }
+
+    /**
+     * Set Char-set value.
+     *
+     * @param charset the Char-set value
+     */
+    @UnsupportedAppUsage
+    public void setCharacterSet(int charset) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        mCharacterSet = charset;
+    }
+
+    /**
+     * Get Text-string value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTextString() {
+        byte[] byteArray = new byte[mData.length];
+
+        System.arraycopy(mData, 0, byteArray, 0, mData.length);
+        return byteArray;
+    }
+
+    /**
+     * Set Text-string value.
+     *
+     * @param textString the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mData = new byte[textString.length];
+        System.arraycopy(textString, 0, mData, 0, textString.length);
+    }
+
+    /**
+     * Convert this object to a {@link java.lang.String}. If the encoding of
+     * the EncodedStringValue is null or unsupported, it will be
+     * treated as iso-8859-1 encoding.
+     *
+     * @return The decoded String.
+     */
+    @UnsupportedAppUsage
+    public String getString()  {
+        if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+            return new String(mData); // system default encoding.
+        } else {
+            try {
+                String name = CharacterSets.getMimeName(mCharacterSet);
+                return new String(mData, name);
+            } catch (UnsupportedEncodingException e) {
+            	if (LOCAL_LOGV) {
+            		Log.v(TAG, e.getMessage(), e);
+            	}
+            	try {
+                    return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+                } catch (UnsupportedEncodingException e2) {
+                    return new String(mData); // system default encoding.
+                }
+            }
+        }
+    }
+
+    /**
+     * Append to Text-string.
+     *
+     * @param textString the textString to append
+     * @throws NullPointerException if the text String is null
+     *                      or an IOException occurred.
+     */
+    @UnsupportedAppUsage
+    public void appendTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("Text-string is null.");
+        }
+
+        if(null == mData) {
+            mData = new byte[textString.length];
+            System.arraycopy(textString, 0, mData, 0, textString.length);
+        } else {
+            ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+            try {
+                newTextString.write(mData);
+                newTextString.write(textString);
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new NullPointerException(
+                        "appendTextString: failed when write a new Text-string");
+            }
+
+            mData = newTextString.toByteArray();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        super.clone();
+        int len = mData.length;
+        byte[] dstBytes = new byte[len];
+        System.arraycopy(mData, 0, dstBytes, 0, len);
+
+        try {
+            return new EncodedStringValue(mCharacterSet, dstBytes);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+            e.printStackTrace();
+            throw new CloneNotSupportedException(e.getMessage());
+        }
+    }
+
+    /**
+     * Split this encoded string around matches of the given pattern.
+     *
+     * @param pattern the delimiting pattern
+     * @return the array of encoded strings computed by splitting this encoded
+     *         string around matches of the given pattern
+     */
+    public EncodedStringValue[] split(String pattern) {
+        String[] temp = getString().split(pattern);
+        EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+        for (int i = 0; i < ret.length; ++i) {
+            try {
+                ret[i] = new EncodedStringValue(mCharacterSet,
+                        temp[i].getBytes());
+            } catch (NullPointerException e) {
+                // Can't arrive here
+                return null;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Extract an EncodedStringValue[] from a given String.
+     */
+    @UnsupportedAppUsage
+    public static EncodedStringValue[] extract(String src) {
+        String[] values = src.split(";");
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < values.length; i++) {
+            if (values[i].length() > 0) {
+                list.add(new EncodedStringValue(values[i]));
+            }
+        }
+
+        int len = list.size();
+        if (len > 0) {
+            return list.toArray(new EncodedStringValue[len]);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Concatenate an EncodedStringValue[] into a single String.
+     */
+    @UnsupportedAppUsage
+    public static String concat(EncodedStringValue[] addr) {
+        StringBuilder sb = new StringBuilder();
+        int maxIndex = addr.length - 1;
+        for (int i = 0; i <= maxIndex; i++) {
+            sb.append(addr[i].getString());
+            if (i < maxIndex) {
+                sb.append(";");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @UnsupportedAppUsage
+    public static EncodedStringValue copy(EncodedStringValue value) {
+        if (value == null) {
+            return null;
+        }
+
+        return new EncodedStringValue(value.mCharacterSet, value.mData);
+    }
+    
+    @UnsupportedAppUsage
+    public static EncodedStringValue[] encodeStrings(String[] array) {
+        int count = array.length;
+        if (count > 0) {
+            EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+            for (int i = 0; i < count; i++) {
+                encodedArray[i] = new EncodedStringValue(array[i]);
+            }
+            return encodedArray;
+        }
+        return null;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/GenericPdu.java b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..ebf16ac
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class GenericPdu {
+    /**
+     * The headers of pdu.
+     */
+    @UnsupportedAppUsage
+    PduHeaders mPduHeaders = null;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu() {
+        mPduHeaders = new PduHeaders();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param headers Headers for this PDU.
+     */
+    GenericPdu(PduHeaders headers) {
+        mPduHeaders = headers;
+    }
+
+    /**
+     * Get the headers of this PDU.
+     *
+     * @return A PduHeaders of this PDU.
+     */
+    @UnsupportedAppUsage
+    PduHeaders getPduHeaders() {
+        return mPduHeaders;
+    }
+
+    /**
+     * Get X-Mms-Message-Type field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public int getMessageType() {
+        return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Set X-Mms-Message-Type field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    @UnsupportedAppUsage
+    public void setMessageType(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Get X-Mms-MMS-Version field value.
+     *
+     * @return the X-Mms-MMS-Version value
+     */
+    public int getMmsVersion() {
+        return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Set X-Mms-MMS-Version field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMmsVersion(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..e108f76
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+    /**
+     * The body.
+     */
+    private PduBody mMessageBody;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public MultimediaMessagePdu() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param header the header of this PDU
+     * @param body the body of this PDU
+     */
+    @UnsupportedAppUsage
+    public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+        super(header);
+        mMessageBody = body;
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    MultimediaMessagePdu(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get body of the PDU.
+     *
+     * @return the body
+     */
+    @UnsupportedAppUsage
+    public PduBody getBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Set body of the PDU.
+     *
+     * @param body the body
+     */
+    @UnsupportedAppUsage
+    public void setBody(PduBody body) {
+        mMessageBody = body;
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Add a "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addTo(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Priority value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getPriority() {
+        return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Set X-Mms-Priority value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setPriority(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value in seconds.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotificationInd.java b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..b561bd4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public NotificationInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    NotificationInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Content-Class Value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getContentClass() {
+        return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Content-Class Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setContentClass(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Content-Location value.
+     * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+     * Content-location-value = Uri-value
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentLocation() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Set X-Mms-Content-Location value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setContentLocation(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Message-Size value.
+     * Message-size-value = Long-integer
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-Message-Size value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report Value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public ElementDescriptorValue getElementDescriptor() {return null;}
+     *     public void getElementDescriptor(ElementDescriptorValue value) {}
+     *
+     *     public byte getPriority() {return 0x00;}
+     *     public void setPriority(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalMode() {return 0x00;}
+     *     public void setRecommendedRetrievalMode(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalModeText() {return 0x00;}
+     *     public void setRecommendedRetrievalModeText(byte value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte getStored() {return 0x00;}
+     *     public void setStored(byte value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..3c70f86
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-NotifyResp.ind pdu.
+     *
+     * @param mmsVersion current version of mms
+     * @param transactionId the transaction-id value
+     * @param status the status value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public NotifyRespInd(int mmsVersion,
+                         byte[] transactionId,
+                         int status) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+        setStatus(status);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    NotifyRespInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Status field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * GetX-Mms-Status field value.
+     *
+     * @return the X-Mms-Status value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduBody.java b/telephony/java/com/google/android/mms/pdu/PduBody.java
new file mode 100644
index 0000000..51914e4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduBody.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+    private Vector<PduPart> mParts = null;
+
+    private Map<String, PduPart> mPartMapByContentId = null;
+    private Map<String, PduPart> mPartMapByContentLocation = null;
+    private Map<String, PduPart> mPartMapByName = null;
+    private Map<String, PduPart> mPartMapByFileName = null;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public PduBody() {
+        mParts = new Vector<PduPart>();
+
+        mPartMapByContentId = new HashMap<String, PduPart>();
+        mPartMapByContentLocation  = new HashMap<String, PduPart>();
+        mPartMapByName = new HashMap<String, PduPart>();
+        mPartMapByFileName = new HashMap<String, PduPart>();
+    }
+
+    private void putPartToMaps(PduPart part) {
+        // Put part to mPartMapByContentId.
+        byte[] contentId = part.getContentId();
+        if(null != contentId) {
+            mPartMapByContentId.put(new String(contentId), part);
+        }
+
+        // Put part to mPartMapByContentLocation.
+        byte[] contentLocation = part.getContentLocation();
+        if(null != contentLocation) {
+            String clc = new String(contentLocation);
+            mPartMapByContentLocation.put(clc, part);
+        }
+
+        // Put part to mPartMapByName.
+        byte[] name = part.getName();
+        if(null != name) {
+            String clc = new String(name);
+            mPartMapByName.put(clc, part);
+        }
+
+        // Put part to mPartMapByFileName.
+        byte[] fileName = part.getFilename();
+        if(null != fileName) {
+            String clc = new String(fileName);
+            mPartMapByFileName.put(clc, part);
+        }
+    }
+
+    /**
+     * Appends the specified part to the end of this body.
+     *
+     * @param part part to be appended
+     * @return true when success, false when fail
+     * @throws NullPointerException when part is null
+     */
+    @UnsupportedAppUsage
+    public boolean addPart(PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        return mParts.add(part);
+    }
+
+    /**
+     * Inserts the specified part at the specified position.
+     *
+     * @param index index at which the specified part is to be inserted
+     * @param part part to be inserted
+     * @throws NullPointerException when part is null
+     */
+    @UnsupportedAppUsage
+    public void addPart(int index, PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        mParts.add(index, part);
+    }
+
+    /**
+     * Removes the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    @UnsupportedAppUsage
+    public PduPart removePart(int index) {
+        return mParts.remove(index);
+    }
+
+    /**
+     * Remove all of the parts.
+     */
+    public void removeAll() {
+        mParts.clear();
+    }
+
+    /**
+     * Get the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    @UnsupportedAppUsage
+    public PduPart getPart(int index) {
+        return mParts.get(index);
+    }
+
+    /**
+     * Get the index of the specified part.
+     *
+     * @param part the part object
+     * @return index the index of the first occurrence of the part in this body
+     */
+    @UnsupportedAppUsage
+    public int getPartIndex(PduPart part) {
+        return mParts.indexOf(part);
+    }
+
+    /**
+     * Get the number of parts.
+     *
+     * @return the number of parts
+     */
+    @UnsupportedAppUsage
+    public int getPartsNum() {
+        return mParts.size();
+    }
+
+    /**
+     * Get pdu part by content id.
+     *
+     * @param cid the value of content id.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByContentId(String cid) {
+        return mPartMapByContentId.get(cid);
+    }
+
+    /**
+     * Get pdu part by Content-Location. Content-Location of part is
+     * the same as filename and name(param of content-type).
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByContentLocation(String contentLocation) {
+        return mPartMapByContentLocation.get(contentLocation);
+    }
+
+    /**
+     * Get pdu part by name.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByName(String name) {
+        return mPartMapByName.get(name);
+    }
+
+    /**
+     * Get pdu part by filename.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByFileName(String filename) {
+        return mPartMapByFileName.get(filename);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduComposer.java b/telephony/java/com/google/android/mms/pdu/PduComposer.java
new file mode 100644
index 0000000..e24bf21
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduComposer.java
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.text.TextUtils;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduComposer {
+    /**
+     * Address type.
+     */
+    static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+    static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
+    static private final int PDU_IPV4_ADDRESS_TYPE = 3;
+    static private final int PDU_IPV6_ADDRESS_TYPE = 4;
+    static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+    /**
+     * Address regular expression string.
+     */
+    static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+    static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+            "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+    static final String REGEXP_IPV6_ADDRESS_TYPE =
+        "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+    static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+            "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+    /**
+     * The postfix strings of address.
+     */
+    static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+    static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+    static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+    /**
+     * Error values.
+     */
+    static private final int PDU_COMPOSE_SUCCESS = 0;
+    static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
+    static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+    static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+    /**
+     * WAP values defined in WSP spec.
+     */
+    static private final int QUOTED_STRING_FLAG = 34;
+    static private final int END_STRING_FLAG = 0;
+    static private final int LENGTH_QUOTE = 31;
+    static private final int TEXT_MAX = 127;
+    static private final int SHORT_INTEGER_MAX = 127;
+    static private final int LONG_INTEGER_LENGTH_MAX = 8;
+
+    /**
+     * Block size when read data from InputStream.
+     */
+    static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+    /**
+     * The output message.
+     */
+    @UnsupportedAppUsage
+    protected ByteArrayOutputStream mMessage = null;
+
+    /**
+     * The PDU.
+     */
+    @UnsupportedAppUsage
+    private GenericPdu mPdu = null;
+
+    /**
+     * Current visiting position of the mMessage.
+     */
+    @UnsupportedAppUsage
+    protected int mPosition = 0;
+
+    /**
+     * Message compose buffer stack.
+     */
+    @UnsupportedAppUsage
+    private BufferStack mStack = null;
+
+    /**
+     * Content resolver.
+     */
+    @UnsupportedAppUsage
+    private final ContentResolver mResolver;
+
+    /**
+     * Header of this pdu.
+     */
+    @UnsupportedAppUsage
+    private PduHeaders mPduHeader = null;
+
+    /**
+     * Map of all content type
+     */
+    @UnsupportedAppUsage
+    private static HashMap<String, Integer> mContentTypeMap = null;
+
+    static {
+        mContentTypeMap = new HashMap<String, Integer>();
+
+        int i;
+        for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+            mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context the context
+     * @param pdu the pdu to be composed
+     */
+    @UnsupportedAppUsage
+    public PduComposer(Context context, GenericPdu pdu) {
+        mPdu = pdu;
+        mResolver = context.getContentResolver();
+        mPduHeader = pdu.getPduHeaders();
+        mStack = new BufferStack();
+        mMessage = new ByteArrayOutputStream();
+        mPosition = 0;
+    }
+
+    /**
+     * Make the message. No need to check whether mandatory fields are set,
+     * because the constructors of outgoing pdus are taking care of this.
+     *
+     * @return OutputStream of maked message. Return null if
+     *         the PDU is invalid.
+     */
+    @UnsupportedAppUsage
+    public byte[] make() {
+        // Get Message-type.
+        int type = mPdu.getMessageType();
+
+        /* make the message */
+        switch (type) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            default:
+                return null;
+        }
+
+        return mMessage.toByteArray();
+    }
+
+    /**
+     *  Copy buf to mMessage.
+     */
+    @UnsupportedAppUsage
+    protected void arraycopy(byte[] buf, int pos, int length) {
+        mMessage.write(buf, pos, length);
+        mPosition = mPosition + length;
+    }
+
+    /**
+     * Append a byte to mMessage.
+     */
+    protected void append(int value) {
+        mMessage.write(value);
+        mPosition ++;
+    }
+
+    /**
+     * Append short integer value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendShortInteger(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-integer = OCTET
+         * ; Integers in range 0-127 shall be encoded as a one octet value
+         * ; with the most significant bit set to one (1xxx xxxx) and with
+         * ; the value in the remaining least significant bits.
+         * In our implementation, only low 7 bits are stored and otherwise
+         * bits are ignored.
+         */
+        append((value | 0x80) & 0xff);
+    }
+
+    /**
+     * Append an octet number between 128 and 255 into mMessage.
+     * NOTE:
+     * A value between 0 and 127 should be appended by using appendShortInteger.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendOctet(int number) {
+        append(number);
+    }
+
+    /**
+     * Append a short length into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortLength(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-length = <Any octet 0-30>
+         */
+        append(value);
+    }
+
+    /**
+     * Append long integer into mMessage. it's used for really long integers.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendLongInteger(long longInt) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Long-integer = Short-length Multi-octet-integer
+         * ; The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * ; The content octets shall be an unsigned integer value with the
+         * ; most significant octet encoded first (big-endian representation).
+         * ; The minimum number of octets must be used to encode the value.
+         */
+        int size;
+        long temp = longInt;
+
+        // Count the length of the long integer.
+        for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+            temp = (temp >>> 8);
+        }
+
+        // Set Length.
+        appendShortLength(size);
+
+        // Count and set the long integer.
+        int i;
+        int shift = (size -1) * 8;
+
+        for (i = 0; i < size; i++) {
+            append((int)((longInt >>> shift) & 0xff));
+            shift = shift - 8;
+        }
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendTextString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
+            append(TEXT_MAX);
+        }
+
+        arraycopy(text, 0, text.length);
+        append(0);
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendTextString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        appendTextString(str.getBytes());
+    }
+
+    /**
+     * Append encoded string value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendEncodedString(EncodedStringValue enStr) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(enStr != null);
+
+        int charset = enStr.getCharacterSet();
+        byte[] textString = enStr.getTextString();
+        if (null == textString) {
+            return;
+        }
+
+        /*
+         * In the implementation of EncodedStringValue, the charset field will
+         * never be 0. It will always be composed as
+         * Encoded-string-value = Value-length Char-set Text-string
+         */
+        mStack.newbuf();
+        PositionMarker start = mStack.mark();
+
+        appendShortInteger(charset);
+        appendTextString(textString);
+
+        int len = start.getLength();
+        mStack.pop();
+        appendValueLength(len);
+        mStack.copy();
+    }
+
+    /**
+     * Append uintvar integer into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendUintvarInteger(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * To encode a large unsigned integer, split it into 7-bit fragments
+         * and place them in the payloads of multiple octets. The most significant
+         * bits are placed in the first octets with the least significant bits
+         * ending up in the last octet. All octets MUST set the Continue bit to 1
+         * except the last octet, which MUST set the Continue bit to 0.
+         */
+        int i;
+        long max = SHORT_INTEGER_MAX;
+
+        for (i = 0; i < 5; i++) {
+            if (value < max) {
+                break;
+            }
+
+            max = (max << 7) | 0x7fl;
+        }
+
+        while(i > 0) {
+            long temp = value >>> (i * 7);
+            temp = temp & 0x7f;
+
+            append((int)((temp | 0x80) & 0xff));
+
+            i--;
+        }
+
+        append((int)(value & 0x7f));
+    }
+
+    /**
+     * Append date value into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendDateValue(long date) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Date-value = Long-integer
+         */
+        appendLongInteger(date);
+    }
+
+    /**
+     * Append value length to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendValueLength(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Value-length = Short-length | (Length-quote Length)
+         * ; Value length is used to indicate the length of the value to follow
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         */
+        if (value < LENGTH_QUOTE) {
+            appendShortLength((int) value);
+            return;
+        }
+
+        append(LENGTH_QUOTE);
+        appendUintvarInteger(value);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendQuotedString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        append(QUOTED_STRING_FLAG);
+        arraycopy(text, 0, text.length);
+        append(END_STRING_FLAG);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendQuotedString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        appendQuotedString(str.getBytes());
+    }
+
+    private EncodedStringValue appendAddressType(EncodedStringValue address) {
+        EncodedStringValue temp = null;
+
+        try {
+            int addressType = checkAddressType(address.getString());
+            temp = EncodedStringValue.copy(address);
+            if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+                // Phone number.
+                temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+                // Ipv4 address.
+                temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+                // Ipv6 address.
+                temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+            }
+        } catch (NullPointerException e) {
+            return null;
+        }
+
+        return temp;
+    }
+
+    /**
+     * Append header to mMessage.
+     */
+    @UnsupportedAppUsage
+    private int appendHeader(int field) {
+        switch (field) {
+            case PduHeaders.MMS_VERSION:
+                appendOctet(field);
+
+                int version = mPduHeader.getOctet(field);
+                if (0 == version) {
+                    appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+                } else {
+                    appendShortInteger(version);
+                }
+
+                break;
+
+            case PduHeaders.MESSAGE_ID:
+            case PduHeaders.TRANSACTION_ID:
+                byte[] textString = mPduHeader.getTextString(field);
+                if (null == textString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendTextString(textString);
+                break;
+
+            case PduHeaders.TO:
+            case PduHeaders.BCC:
+            case PduHeaders.CC:
+                EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+                if (null == addr) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                EncodedStringValue temp;
+                for (int i = 0; i < addr.length; i++) {
+                    temp = appendAddressType(addr[i]);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendOctet(field);
+                    appendEncodedString(temp);
+                }
+                break;
+
+            case PduHeaders.FROM:
+                // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+                appendOctet(field);
+
+                EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+                if ((from == null)
+                        || TextUtils.isEmpty(from.getString())
+                        || new String(from.getTextString()).equals(
+                                PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+                    // Length of from = 1
+                    append(1);
+                    // Insert-address-token = <Octet 129>
+                    append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+                } else {
+                    mStack.newbuf();
+                    PositionMarker fstart = mStack.mark();
+
+                    // Address-present-token = <Octet 128>
+                    append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+
+                    temp = appendAddressType(from);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendEncodedString(temp);
+
+                    int flen = fstart.getLength();
+                    mStack.pop();
+                    appendValueLength(flen);
+                    mStack.copy();
+                }
+                break;
+
+            case PduHeaders.READ_STATUS:
+            case PduHeaders.STATUS:
+            case PduHeaders.REPORT_ALLOWED:
+            case PduHeaders.PRIORITY:
+            case PduHeaders.DELIVERY_REPORT:
+            case PduHeaders.READ_REPORT:
+            case PduHeaders.RETRIEVE_STATUS:
+                int octet = mPduHeader.getOctet(field);
+                if (0 == octet) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendOctet(octet);
+                break;
+
+            case PduHeaders.DATE:
+                long date = mPduHeader.getLongInteger(field);
+                if (-1 == date) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendDateValue(date);
+                break;
+
+            case PduHeaders.SUBJECT:
+            case PduHeaders.RETRIEVE_TEXT:
+                EncodedStringValue enString =
+                    mPduHeader.getEncodedStringValue(field);
+                if (null == enString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendEncodedString(enString);
+                break;
+
+            case PduHeaders.MESSAGE_CLASS:
+                byte[] messageClass = mPduHeader.getTextString(field);
+                if (null == messageClass) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+                } else {
+                    appendTextString(messageClass);
+                }
+                break;
+
+            case PduHeaders.EXPIRY:
+                long expiry = mPduHeader.getLongInteger(field);
+                if (-1 == expiry) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+
+                mStack.newbuf();
+                PositionMarker expiryStart = mStack.mark();
+
+                append(PduHeaders.VALUE_RELATIVE_TOKEN);
+                appendLongInteger(expiry);
+
+                int expiryLength = expiryStart.getLength();
+                mStack.pop();
+                appendValueLength(expiryLength);
+                mStack.copy();
+                break;
+
+            default:
+                return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make ReadRec.Ind.
+     */
+    private int makeReadRecInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Message-ID
+        if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Optional
+        appendHeader(PduHeaders.DATE);
+
+        // X-Mms-Read-Status
+        if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Applic-ID Optional(not support)
+        // X-Mms-Reply-Applic-ID Optional(not support)
+        // X-Mms-Aux-Applic-Info Optional(not support)
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make NotifyResp.Ind.
+     */
+    private int makeNotifyResp() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //  X-Mms-Status
+        if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional (not support)
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Acknowledge.Ind.
+     */
+    private int makeAckInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //     X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional
+        appendHeader(PduHeaders.REPORT_ALLOWED);
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Send.req.
+     */
+    private int makeSendRetrievePdu(int type) {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(type);
+
+        // X-Mms-Transaction-ID
+        appendOctet(PduHeaders.TRANSACTION_ID);
+
+        byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+        if (trid == null) {
+            // Transaction-ID should be set(by Transaction) before make().
+            throw new IllegalArgumentException("Transaction-ID is null.");
+        }
+        appendTextString(trid);
+
+        //  X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Date-value Optional.
+        appendHeader(PduHeaders.DATE);
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        boolean recipient = false;
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Cc
+        if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Bcc
+        if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Need at least one of "cc", "bcc" and "to".
+        if (false == recipient) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Subject Optional
+        appendHeader(PduHeaders.SUBJECT);
+
+        // X-Mms-Message-Class Optional
+        // Message-class-value = Class-identifier | Token-text
+        appendHeader(PduHeaders.MESSAGE_CLASS);
+
+        // X-Mms-Expiry Optional
+        appendHeader(PduHeaders.EXPIRY);
+
+        // X-Mms-Priority Optional
+        appendHeader(PduHeaders.PRIORITY);
+
+        // X-Mms-Delivery-Report Optional
+        appendHeader(PduHeaders.DELIVERY_REPORT);
+
+        // X-Mms-Read-Report Optional
+        appendHeader(PduHeaders.READ_REPORT);
+
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            // X-Mms-Retrieve-Status Optional
+            appendHeader(PduHeaders.RETRIEVE_STATUS);
+            // X-Mms-Retrieve-Text Optional
+            appendHeader(PduHeaders.RETRIEVE_TEXT);
+        }
+
+
+        //    Content-Type
+        appendOctet(PduHeaders.CONTENT_TYPE);
+
+        //  Message body
+        return makeMessageBody(type);
+    }
+
+    /**
+     * Make message body.
+     */
+    private int makeMessageBody(int type) {
+        // 1. add body informations
+        mStack.newbuf();  // Switching buffer because we need to
+
+        PositionMarker ctStart = mStack.mark();
+
+        // This contentTypeIdentifier should be used for type of attachment...
+        String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+        Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+        if (contentTypeIdentifier == null) {
+            // content type is mandatory
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        appendShortInteger(contentTypeIdentifier.intValue());
+
+        // content-type parameter: start
+        PduBody body;
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            body = ((RetrieveConf) mPdu).getBody();
+        } else {
+            body = ((SendReq) mPdu).getBody();
+        }
+        if (null == body || body.getPartsNum() == 0) {
+            // empty message
+            appendUintvarInteger(0);
+            mStack.pop();
+            mStack.copy();
+            return PDU_COMPOSE_SUCCESS;
+        }
+
+        PduPart part;
+        try {
+            part = body.getPart(0);
+
+            byte[] start = part.getContentId();
+            if (start != null) {
+                appendOctet(PduPart.P_DEP_START);
+                if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+                    appendTextString(start);
+                } else {
+                    appendTextString("<" + new String(start) + ">");
+                }
+            }
+
+            // content-type parameter: type
+            appendOctet(PduPart.P_CT_MR_TYPE);
+            appendTextString(part.getContentType());
+        }
+        catch (ArrayIndexOutOfBoundsException e){
+            e.printStackTrace();
+        }
+
+        int ctLength = ctStart.getLength();
+        mStack.pop();
+        appendValueLength(ctLength);
+        mStack.copy();
+
+        // 3. add content
+        int partNum = body.getPartsNum();
+        appendUintvarInteger(partNum);
+        for (int i = 0; i < partNum; i++) {
+            part = body.getPart(i);
+            mStack.newbuf();  // Leaving space for header lengh and data length
+            PositionMarker attachment = mStack.mark();
+
+            mStack.newbuf();  // Leaving space for Content-Type length
+            PositionMarker contentTypeBegin = mStack.mark();
+
+            byte[] partContentType = part.getContentType();
+
+            if (partContentType == null) {
+                // content type is mandatory
+                return PDU_COMPOSE_CONTENT_ERROR;
+            }
+
+            // content-type value
+            Integer partContentTypeIdentifier =
+                mContentTypeMap.get(new String(partContentType));
+            if (partContentTypeIdentifier == null) {
+                appendTextString(partContentType);
+            } else {
+                appendShortInteger(partContentTypeIdentifier.intValue());
+            }
+
+            /* Content-type parameter : name.
+             * The value of name, filename, content-location is the same.
+             * Just one of them is enough for this PDU.
+             */
+            byte[] name = part.getName();
+
+            if (null == name) {
+                name = part.getFilename();
+
+                if (null == name) {
+                    name = part.getContentLocation();
+
+                    if (null == name) {
+                        /* at lease one of name, filename, Content-location
+                         * should be available.
+                         */
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+                }
+            }
+            appendOctet(PduPart.P_DEP_NAME);
+            appendTextString(name);
+
+            // content-type parameter : charset
+            int charset = part.getCharset();
+            if (charset != 0) {
+                appendOctet(PduPart.P_CHARSET);
+                appendShortInteger(charset);
+            }
+
+            int contentTypeLength = contentTypeBegin.getLength();
+            mStack.pop();
+            appendValueLength(contentTypeLength);
+            mStack.copy();
+
+            // content id
+            byte[] contentId = part.getContentId();
+
+            if (null != contentId) {
+                appendOctet(PduPart.P_CONTENT_ID);
+                if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+                    appendQuotedString(contentId);
+                } else {
+                    appendQuotedString("<" + new String(contentId) + ">");
+                }
+            }
+
+            // content-location
+            byte[] contentLocation = part.getContentLocation();
+            if (null != contentLocation) {
+            	appendOctet(PduPart.P_CONTENT_LOCATION);
+            	appendTextString(contentLocation);
+            }
+
+            // content
+            int headerLength = attachment.getLength();
+
+            int dataLength = 0; // Just for safety...
+            byte[] partData = part.getData();
+
+            if (partData != null) {
+                arraycopy(partData, 0, partData.length);
+                dataLength = partData.length;
+            } else {
+                InputStream cr = null;
+                try {
+                    byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+                    cr = mResolver.openInputStream(part.getDataUri());
+                    int len = 0;
+                    while ((len = cr.read(buffer)) != -1) {
+                        mMessage.write(buffer, 0, len);
+                        mPosition += len;
+                        dataLength += len;
+                    }
+                } catch (FileNotFoundException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (IOException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (RuntimeException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } finally {
+                    if (cr != null) {
+                        try {
+                            cr.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+
+            if (dataLength != (attachment.getLength() - headerLength)) {
+                throw new RuntimeException("BUG: Length sanity check failed");
+            }
+
+            mStack.pop();
+            appendUintvarInteger(headerLength);
+            appendUintvarInteger(dataLength);
+            mStack.copy();
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     *  Record current message informations.
+     */
+    static private class LengthRecordNode {
+        ByteArrayOutputStream currentMessage = null;
+        public int currentPosition = 0;
+
+        public LengthRecordNode next = null;
+    }
+
+    /**
+     * Mark current message position and stact size.
+     */
+    private class PositionMarker {
+        private int c_pos;   // Current position
+        private int currentStackSize;  // Current stack size
+
+        @UnsupportedAppUsage
+        int getLength() {
+            // If these assert fails, likely that you are finding the
+            // size of buffer that is deep in BufferStack you can only
+            // find the length of the buffer that is on top
+            if (currentStackSize != mStack.stackSize) {
+                throw new RuntimeException("BUG: Invalid call to getLength()");
+            }
+
+            return mPosition - c_pos;
+        }
+    }
+
+    /**
+     * This implementation can be OPTIMIZED to use only
+     * 2 buffers. This optimization involves changing BufferStack
+     * only... Its usage (interface) will not change.
+     */
+    private class BufferStack {
+        private LengthRecordNode stack = null;
+        private LengthRecordNode toCopy = null;
+
+        int stackSize = 0;
+
+        /**
+         *  Create a new message buffer and push it into the stack.
+         */
+        @UnsupportedAppUsage
+        void newbuf() {
+            // You can't create a new buff when toCopy != null
+            // That is after calling pop() and before calling copy()
+            // If you do, it is a bug
+            if (toCopy != null) {
+                throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+            }
+
+            LengthRecordNode temp = new LengthRecordNode();
+
+            temp.currentMessage = mMessage;
+            temp.currentPosition = mPosition;
+
+            temp.next = stack;
+            stack = temp;
+
+            stackSize = stackSize + 1;
+
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        /**
+         *  Pop the message before and record current message in the stack.
+         */
+        @UnsupportedAppUsage
+        void pop() {
+            ByteArrayOutputStream currentMessage = mMessage;
+            int currentPosition = mPosition;
+
+            mMessage = stack.currentMessage;
+            mPosition = stack.currentPosition;
+
+            toCopy = stack;
+            // Re using the top element of the stack to avoid memory allocation
+
+            stack = stack.next;
+            stackSize = stackSize - 1;
+
+            toCopy.currentMessage = currentMessage;
+            toCopy.currentPosition = currentPosition;
+        }
+
+        /**
+         *  Append current message to the message before.
+         */
+        @UnsupportedAppUsage
+        void copy() {
+            arraycopy(toCopy.currentMessage.toByteArray(), 0,
+                    toCopy.currentPosition);
+
+            toCopy = null;
+        }
+
+        /**
+         *  Mark current message position
+         */
+        @UnsupportedAppUsage
+        PositionMarker mark() {
+            PositionMarker m = new PositionMarker();
+
+            m.c_pos = mPosition;
+            m.currentStackSize = stackSize;
+
+            return m;
+        }
+    }
+
+    /**
+     * Check address type.
+     *
+     * @param address address string without the postfix stinng type,
+     *        such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+     * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+     *         PDU_EMAIL_ADDRESS_TYPE if it is email address,
+     *         PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+     *         PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+     *         PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+     */
+    protected static int checkAddressType(String address) {
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+         * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+         * e-mail = mailbox; to the definition of mailbox as described in
+         * section 3.4 of [RFC2822], but excluding the
+         * obsolete definitions as indicated by the "obs-" prefix.
+         * device-address = ( global-phone-number "/TYPE=PLMN" )
+         * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+         * / ( escaped-value "/TYPE=" address-type )
+         *
+         * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+         * written-sep =("-"/".")
+         *
+         * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+         *
+         * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+         */
+
+        if (null == address) {
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+
+        if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+            // Ipv4 address.
+            return PDU_IPV4_ADDRESS_TYPE;
+        }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+            // Phone number.
+            return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+            // Email address.
+            return PDU_EMAIL_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+            // Ipv6 address.
+            return PDU_IPV6_ADDRESS_TYPE;
+        } else {
+            // Unknown address.
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduContentTypes.java b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..8551b2f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class PduContentTypes {
+    /**
+     * All content types. From:
+     * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+     */
+    @UnsupportedAppUsage
+    static final String[] contentTypes = {
+        "*/*",                                        /* 0x00 */
+        "text/*",                                     /* 0x01 */
+        "text/html",                                  /* 0x02 */
+        "text/plain",                                 /* 0x03 */
+        "text/x-hdml",                                /* 0x04 */
+        "text/x-ttml",                                /* 0x05 */
+        "text/x-vCalendar",                           /* 0x06 */
+        "text/x-vCard",                               /* 0x07 */
+        "text/vnd.wap.wml",                           /* 0x08 */
+        "text/vnd.wap.wmlscript",                     /* 0x09 */
+        "text/vnd.wap.wta-event",                     /* 0x0A */
+        "multipart/*",                                /* 0x0B */
+        "multipart/mixed",                            /* 0x0C */
+        "multipart/form-data",                        /* 0x0D */
+        "multipart/byterantes",                       /* 0x0E */
+        "multipart/alternative",                      /* 0x0F */
+        "application/*",                              /* 0x10 */
+        "application/java-vm",                        /* 0x11 */
+        "application/x-www-form-urlencoded",          /* 0x12 */
+        "application/x-hdmlc",                        /* 0x13 */
+        "application/vnd.wap.wmlc",                   /* 0x14 */
+        "application/vnd.wap.wmlscriptc",             /* 0x15 */
+        "application/vnd.wap.wta-eventc",             /* 0x16 */
+        "application/vnd.wap.uaprof",                 /* 0x17 */
+        "application/vnd.wap.wtls-ca-certificate",    /* 0x18 */
+        "application/vnd.wap.wtls-user-certificate",  /* 0x19 */
+        "application/x-x509-ca-cert",                 /* 0x1A */
+        "application/x-x509-user-cert",               /* 0x1B */
+        "image/*",                                    /* 0x1C */
+        "image/gif",                                  /* 0x1D */
+        "image/jpeg",                                 /* 0x1E */
+        "image/tiff",                                 /* 0x1F */
+        "image/png",                                  /* 0x20 */
+        "image/vnd.wap.wbmp",                         /* 0x21 */
+        "application/vnd.wap.multipart.*",            /* 0x22 */
+        "application/vnd.wap.multipart.mixed",        /* 0x23 */
+        "application/vnd.wap.multipart.form-data",    /* 0x24 */
+        "application/vnd.wap.multipart.byteranges",   /* 0x25 */
+        "application/vnd.wap.multipart.alternative",  /* 0x26 */
+        "application/xml",                            /* 0x27 */
+        "text/xml",                                   /* 0x28 */
+        "application/vnd.wap.wbxml",                  /* 0x29 */
+        "application/x-x968-cross-cert",              /* 0x2A */
+        "application/x-x968-ca-cert",                 /* 0x2B */
+        "application/x-x968-user-cert",               /* 0x2C */
+        "text/vnd.wap.si",                            /* 0x2D */
+        "application/vnd.wap.sic",                    /* 0x2E */
+        "text/vnd.wap.sl",                            /* 0x2F */
+        "application/vnd.wap.slc",                    /* 0x30 */
+        "text/vnd.wap.co",                            /* 0x31 */
+        "application/vnd.wap.coc",                    /* 0x32 */
+        "application/vnd.wap.multipart.related",      /* 0x33 */
+        "application/vnd.wap.sia",                    /* 0x34 */
+        "text/vnd.wap.connectivity-xml",              /* 0x35 */
+        "application/vnd.wap.connectivity-wbxml",     /* 0x36 */
+        "application/pkcs7-mime",                     /* 0x37 */
+        "application/vnd.wap.hashed-certificate",     /* 0x38 */
+        "application/vnd.wap.signed-certificate",     /* 0x39 */
+        "application/vnd.wap.cert-response",          /* 0x3A */
+        "application/xhtml+xml",                      /* 0x3B */
+        "application/wml+xml",                        /* 0x3C */
+        "text/css",                                   /* 0x3D */
+        "application/vnd.wap.mms-message",            /* 0x3E */
+        "application/vnd.wap.rollover-certificate",   /* 0x3F */
+        "application/vnd.wap.locc+wbxml",             /* 0x40 */
+        "application/vnd.wap.loc+xml",                /* 0x41 */
+        "application/vnd.syncml.dm+wbxml",            /* 0x42 */
+        "application/vnd.syncml.dm+xml",              /* 0x43 */
+        "application/vnd.syncml.notification",        /* 0x44 */
+        "application/vnd.wap.xhtml+xml",              /* 0x45 */
+        "application/vnd.wv.csp.cir",                 /* 0x46 */
+        "application/vnd.oma.dd+xml",                 /* 0x47 */
+        "application/vnd.oma.drm.message",            /* 0x48 */
+        "application/vnd.oma.drm.content",            /* 0x49 */
+        "application/vnd.oma.drm.rights+xml",         /* 0x4A */
+        "application/vnd.oma.drm.rights+wbxml",       /* 0x4B */
+        "application/vnd.wv.csp+xml",                 /* 0x4C */
+        "application/vnd.wv.csp+wbxml",               /* 0x4D */
+        "application/vnd.syncml.ds.notification",     /* 0x4E */
+        "audio/*",                                    /* 0x4F */
+        "video/*",                                    /* 0x50 */
+        "application/vnd.oma.dd2+xml",                /* 0x51 */
+        "application/mikey"                           /* 0x52 */
+    };
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduHeaders.java b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
new file mode 100644
index 0000000..b524464
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+    /**
+     * All pdu header fields.
+     */
+    public static final int BCC                             = 0x81;
+    public static final int CC                              = 0x82;
+    public static final int CONTENT_LOCATION                = 0x83;
+    public static final int CONTENT_TYPE                    = 0x84;
+    public static final int DATE                            = 0x85;
+    public static final int DELIVERY_REPORT                 = 0x86;
+    public static final int DELIVERY_TIME                   = 0x87;
+    public static final int EXPIRY                          = 0x88;
+    public static final int FROM                            = 0x89;
+    public static final int MESSAGE_CLASS                   = 0x8A;
+    public static final int MESSAGE_ID                      = 0x8B;
+    public static final int MESSAGE_TYPE                    = 0x8C;
+    public static final int MMS_VERSION                     = 0x8D;
+    public static final int MESSAGE_SIZE                    = 0x8E;
+    public static final int PRIORITY                        = 0x8F;
+
+    public static final int READ_REPLY                      = 0x90;
+    public static final int READ_REPORT                     = 0x90;
+    public static final int REPORT_ALLOWED                  = 0x91;
+    public static final int RESPONSE_STATUS                 = 0x92;
+    public static final int RESPONSE_TEXT                   = 0x93;
+    public static final int SENDER_VISIBILITY               = 0x94;
+    public static final int STATUS                          = 0x95;
+    public static final int SUBJECT                         = 0x96;
+    public static final int TO                              = 0x97;
+    public static final int TRANSACTION_ID                  = 0x98;
+    public static final int RETRIEVE_STATUS                 = 0x99;
+    public static final int RETRIEVE_TEXT                   = 0x9A;
+    public static final int READ_STATUS                     = 0x9B;
+    public static final int REPLY_CHARGING                  = 0x9C;
+    public static final int REPLY_CHARGING_DEADLINE         = 0x9D;
+    public static final int REPLY_CHARGING_ID               = 0x9E;
+    public static final int REPLY_CHARGING_SIZE             = 0x9F;
+
+    public static final int PREVIOUSLY_SENT_BY              = 0xA0;
+    public static final int PREVIOUSLY_SENT_DATE            = 0xA1;
+    public static final int STORE                           = 0xA2;
+    public static final int MM_STATE                        = 0xA3;
+    public static final int MM_FLAGS                        = 0xA4;
+    public static final int STORE_STATUS                    = 0xA5;
+    public static final int STORE_STATUS_TEXT               = 0xA6;
+    public static final int STORED                          = 0xA7;
+    public static final int ATTRIBUTES                      = 0xA8;
+    public static final int TOTALS                          = 0xA9;
+    public static final int MBOX_TOTALS                     = 0xAA;
+    public static final int QUOTAS                          = 0xAB;
+    public static final int MBOX_QUOTAS                     = 0xAC;
+    public static final int MESSAGE_COUNT                   = 0xAD;
+    public static final int CONTENT                         = 0xAE;
+    public static final int START                           = 0xAF;
+
+    public static final int ADDITIONAL_HEADERS              = 0xB0;
+    public static final int DISTRIBUTION_INDICATOR          = 0xB1;
+    public static final int ELEMENT_DESCRIPTOR              = 0xB2;
+    public static final int LIMIT                           = 0xB3;
+    public static final int RECOMMENDED_RETRIEVAL_MODE      = 0xB4;
+    public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+    public static final int STATUS_TEXT                     = 0xB6;
+    public static final int APPLIC_ID                       = 0xB7;
+    public static final int REPLY_APPLIC_ID                 = 0xB8;
+    public static final int AUX_APPLIC_ID                   = 0xB9;
+    public static final int CONTENT_CLASS                   = 0xBA;
+    public static final int DRM_CONTENT                     = 0xBB;
+    public static final int ADAPTATION_ALLOWED              = 0xBC;
+    public static final int REPLACE_ID                      = 0xBD;
+    public static final int CANCEL_ID                       = 0xBE;
+    public static final int CANCEL_STATUS                   = 0xBF;
+
+    /**
+     * X-Mms-Message-Type field types.
+     */
+    public static final int MESSAGE_TYPE_SEND_REQ           = 0x80;
+    public static final int MESSAGE_TYPE_SEND_CONF          = 0x81;
+    public static final int MESSAGE_TYPE_NOTIFICATION_IND   = 0x82;
+    public static final int MESSAGE_TYPE_NOTIFYRESP_IND     = 0x83;
+    public static final int MESSAGE_TYPE_RETRIEVE_CONF      = 0x84;
+    public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND    = 0x85;
+    public static final int MESSAGE_TYPE_DELIVERY_IND       = 0x86;
+    public static final int MESSAGE_TYPE_READ_REC_IND       = 0x87;
+    public static final int MESSAGE_TYPE_READ_ORIG_IND      = 0x88;
+    public static final int MESSAGE_TYPE_FORWARD_REQ        = 0x89;
+    public static final int MESSAGE_TYPE_FORWARD_CONF       = 0x8A;
+    public static final int MESSAGE_TYPE_MBOX_STORE_REQ     = 0x8B;
+    public static final int MESSAGE_TYPE_MBOX_STORE_CONF    = 0x8C;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_REQ      = 0x8D;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_CONF     = 0x8E;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ    = 0x8F;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF   = 0x90;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_REQ    = 0x91;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_CONF   = 0x92;
+    public static final int MESSAGE_TYPE_MBOX_DESCR         = 0x93;
+    public static final int MESSAGE_TYPE_DELETE_REQ         = 0x94;
+    public static final int MESSAGE_TYPE_DELETE_CONF        = 0x95;
+    public static final int MESSAGE_TYPE_CANCEL_REQ         = 0x96;
+    public static final int MESSAGE_TYPE_CANCEL_CONF        = 0x97;
+
+    /**
+     *  X-Mms-Delivery-Report |
+     *  X-Mms-Read-Report |
+     *  X-Mms-Report-Allowed |
+     *  X-Mms-Sender-Visibility |
+     *  X-Mms-Store |
+     *  X-Mms-Stored |
+     *  X-Mms-Totals |
+     *  X-Mms-Quotas |
+     *  X-Mms-Distribution-Indicator |
+     *  X-Mms-DRM-Content |
+     *  X-Mms-Adaptation-Allowed |
+     *  field types.
+     */
+    public static final int VALUE_YES                       = 0x80;
+    public static final int VALUE_NO                        = 0x81;
+
+    /**
+     *  Delivery-Time |
+     *  Expiry and Reply-Charging-Deadline |
+     *  field type components.
+     */
+    public static final int VALUE_ABSOLUTE_TOKEN            = 0x80;
+    public static final int VALUE_RELATIVE_TOKEN            = 0x81;
+
+    /**
+     * X-Mms-MMS-Version field types.
+     */
+    public static final int MMS_VERSION_1_3                 = ((1 << 4) | 3);
+    public static final int MMS_VERSION_1_2                 = ((1 << 4) | 2);
+    public static final int MMS_VERSION_1_1                 = ((1 << 4) | 1);
+    public static final int MMS_VERSION_1_0                 = ((1 << 4) | 0);
+
+    // Current version is 1.2.
+    public static final int CURRENT_MMS_VERSION             = MMS_VERSION_1_2;
+
+    /**
+     *  From field type components.
+     */
+    public static final int FROM_ADDRESS_PRESENT_TOKEN      = 0x80;
+    public static final int FROM_INSERT_ADDRESS_TOKEN       = 0x81;
+
+    public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+    public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+    /**
+     *  X-Mms-Status Field.
+     */
+    public static final int STATUS_EXPIRED                  = 0x80;
+    public static final int STATUS_RETRIEVED                = 0x81;
+    public static final int STATUS_REJECTED                 = 0x82;
+    public static final int STATUS_DEFERRED                 = 0x83;
+    public static final int STATUS_UNRECOGNIZED             = 0x84;
+    public static final int STATUS_INDETERMINATE            = 0x85;
+    public static final int STATUS_FORWARDED                = 0x86;
+    public static final int STATUS_UNREACHABLE              = 0x87;
+
+    /**
+     *  MM-Flags field type components.
+     */
+    public static final int MM_FLAGS_ADD_TOKEN              = 0x80;
+    public static final int MM_FLAGS_REMOVE_TOKEN           = 0x81;
+    public static final int MM_FLAGS_FILTER_TOKEN           = 0x82;
+
+    /**
+     *  X-Mms-Message-Class field types.
+     */
+    public static final int MESSAGE_CLASS_PERSONAL          = 0x80;
+    public static final int MESSAGE_CLASS_ADVERTISEMENT     = 0x81;
+    public static final int MESSAGE_CLASS_INFORMATIONAL     = 0x82;
+    public static final int MESSAGE_CLASS_AUTO              = 0x83;
+
+    public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+    public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+    public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+    public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+    /**
+     *  X-Mms-Priority field types.
+     */
+    public static final int PRIORITY_LOW                    = 0x80;
+    public static final int PRIORITY_NORMAL                 = 0x81;
+    public static final int PRIORITY_HIGH                   = 0x82;
+
+    /**
+     *  X-Mms-Response-Status field types.
+     */
+    public static final int RESPONSE_STATUS_OK                   = 0x80;
+    public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED    = 0x81;
+    public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT     = 0x83;
+    public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND    = 0x85;
+    public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM      = 0x86;
+    public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+    public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE  = 0x88;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE    = 0xC0;
+
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND         = 0xC2;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM           = 0xC3;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS           = 0xC4;
+
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE                             = 0xE0;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED                      = 0xE1;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT              = 0xE2;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED          = 0xE3;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND                   = 0xE4;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED                = 0xE5;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET  = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED    = 0xE8;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED        = 0xE9;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED        = 0xEA;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID                     = 0xEB;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_END                                 = 0xFF;
+
+    /**
+     *  X-Mms-Retrieve-Status field types.
+     */
+    public static final int RETRIEVE_STATUS_OK                                  = 0x80;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE             = 0xC0;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 0xC1;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM     = 0xC2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE             = 0xE0;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED      = 0xE1;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 0xE2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+    public static final int RETRIEVE_STATUS_ERROR_END                           = 0xFF;
+
+    /**
+     *  X-Mms-Sender-Visibility field types.
+     */
+    public static final int SENDER_VISIBILITY_HIDE          = 0x80;
+    public static final int SENDER_VISIBILITY_SHOW          = 0x81;
+
+    /**
+     *  X-Mms-Read-Status field types.
+     */
+    public static final int READ_STATUS_READ                        = 0x80;
+    public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+    /**
+     *  X-Mms-Cancel-Status field types.
+     */
+    public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+    public static final int CANCEL_STATUS_REQUEST_CORRUPTED             = 0x81;
+
+    /**
+     *  X-Mms-Reply-Charging field types.
+     */
+    public static final int REPLY_CHARGING_REQUESTED           = 0x80;
+    public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+    public static final int REPLY_CHARGING_ACCEPTED            = 0x82;
+    public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY  = 0x83;
+
+    /**
+     *  X-Mms-MM-State field types.
+     */
+    public static final int MM_STATE_DRAFT                  = 0x80;
+    public static final int MM_STATE_SENT                   = 0x81;
+    public static final int MM_STATE_NEW                    = 0x82;
+    public static final int MM_STATE_RETRIEVED              = 0x83;
+    public static final int MM_STATE_FORWARDED              = 0x84;
+
+    /**
+     * X-Mms-Recommended-Retrieval-Mode field types.
+     */
+    public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+    /**
+     *  X-Mms-Content-Class field types.
+     */
+    public static final int CONTENT_CLASS_TEXT              = 0x80;
+    public static final int CONTENT_CLASS_IMAGE_BASIC       = 0x81;
+    public static final int CONTENT_CLASS_IMAGE_RICH        = 0x82;
+    public static final int CONTENT_CLASS_VIDEO_BASIC       = 0x83;
+    public static final int CONTENT_CLASS_VIDEO_RICH        = 0x84;
+    public static final int CONTENT_CLASS_MEGAPIXEL         = 0x85;
+    public static final int CONTENT_CLASS_CONTENT_BASIC     = 0x86;
+    public static final int CONTENT_CLASS_CONTENT_RICH      = 0x87;
+
+    /**
+     *  X-Mms-Store-Status field types.
+     */
+    public static final int STORE_STATUS_SUCCESS                                = 0x80;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE                = 0xC0;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM        = 0xC1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE                = 0xE0;
+    public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED         = 0xE1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND      = 0xE3;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL             = 0xE4;
+    public static final int STORE_STATUS_ERROR_END                              = 0xFF;
+
+    /**
+     * The map contains the value of all headers.
+     */
+    private HashMap<Integer, Object> mHeaderMap = null;
+
+    /**
+     * Constructor of PduHeaders.
+     */
+    @UnsupportedAppUsage
+    public PduHeaders() {
+        mHeaderMap = new HashMap<Integer, Object>();
+    }
+
+    /**
+     * Get octet value by header field.
+     *
+     * @param field the field
+     * @return the octet value of the pdu header
+     *          with specified header field. Return 0 if
+     *          the value is not set.
+     */
+    @UnsupportedAppUsage
+    protected int getOctet(int field) {
+        Integer octet = (Integer) mHeaderMap.get(field);
+        if (null == octet) {
+            return 0;
+        }
+
+        return octet;
+    }
+
+    /**
+     * Set octet value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    protected void setOctet(int value, int field)
+            throws InvalidHeaderValueException{
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case REPORT_ALLOWED:
+            case ADAPTATION_ALLOWED:
+            case DELIVERY_REPORT:
+            case DRM_CONTENT:
+            case DISTRIBUTION_INDICATOR:
+            case QUOTAS:
+            case READ_REPORT:
+            case STORE:
+            case STORED:
+            case TOTALS:
+            case SENDER_VISIBILITY:
+                if ((VALUE_YES != value) && (VALUE_NO != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case READ_STATUS:
+                if ((READ_STATUS_READ != value) &&
+                        (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CANCEL_STATUS:
+                if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+                        (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case PRIORITY:
+                if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case STATUS:
+                if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case REPLY_CHARGING:
+                if ((value < REPLY_CHARGING_REQUESTED)
+                        || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case MM_STATE:
+                if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RECOMMENDED_RETRIEVAL_MODE:
+                if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CONTENT_CLASS:
+                if ((value < CONTENT_CLASS_TEXT)
+                        || (value > CONTENT_CLASS_CONTENT_RICH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RETRIEVE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+                if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+                        (value <= RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < RETRIEVE_STATUS_OK) ||
+                        ((value > RETRIEVE_STATUS_OK) &&
+                                (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case STORE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+                if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+                        (value <= STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < STORE_STATUS_SUCCESS) ||
+                        ((value > STORE_STATUS_SUCCESS) &&
+                                (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case RESPONSE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+                if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+                        (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+                        (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+                        (value < RESPONSE_STATUS_OK) ||
+                        ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+                                (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+                    value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case MMS_VERSION:
+                if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+                    value = CURRENT_MMS_VERSION; // Current version is the default value.
+                }
+                break;
+            case MESSAGE_TYPE:
+                if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            default:
+                // This header value should not be Octect.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get TextString value by header field.
+     *
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected byte[] getTextString(int field) {
+        return (byte[]) mHeaderMap.get(field);
+    }
+
+    /**
+     * Set TextString value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setTextString(byte[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case TRANSACTION_ID:
+            case REPLY_CHARGING_ID:
+            case AUX_APPLIC_ID:
+            case APPLIC_ID:
+            case REPLY_APPLIC_ID:
+            case MESSAGE_ID:
+            case REPLACE_ID:
+            case CANCEL_ID:
+            case CONTENT_LOCATION:
+            case MESSAGE_CLASS:
+            case CONTENT_TYPE:
+                break;
+            default:
+                // This header value should not be Text-String.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get EncodedStringValue value by header field.
+     *
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected EncodedStringValue getEncodedStringValue(int field) {
+        return (EncodedStringValue) mHeaderMap.get(field);
+    }
+
+    /**
+     * Get TO, CC or BCC header value.
+     *
+     * @param field the field
+     * @return the EncodeStringValue array of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected EncodedStringValue[] getEncodedStringValues(int field) {
+        ArrayList<EncodedStringValue> list =
+                (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            return null;
+        }
+        EncodedStringValue[] values = new EncodedStringValue[list.size()];
+        return list.toArray(values);
+    }
+
+    /**
+     * Set EncodedStringValue value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    protected void setEncodedStringValue(EncodedStringValue value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case SUBJECT:
+            case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+            case RETRIEVE_TEXT:
+            case STATUS_TEXT:
+            case STORE_STATUS_TEXT:
+            case RESPONSE_TEXT:
+            case FROM:
+            case PREVIOUSLY_SENT_BY:
+            case MM_FLAGS:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Set TO, CC or BCC header value.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value array of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < value.length; i++) {
+            list.add(value[i]);
+        }
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Append one EncodedStringValue to another.
+     *
+     * @param value the EncodedStringValue to append
+     * @param field the field
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    protected void appendEncodedStringValue(EncodedStringValue value,
+                                    int field) {
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list =
+            (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            list  = new ArrayList<EncodedStringValue>();
+        }
+        list.add(value);
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Get LongInteger value by header field.
+     *
+     * @param field the field
+     * @return the LongInteger value of the pdu header
+     *          with specified header field. if return -1, the
+     *          field is not existed in pdu header.
+     */
+    @UnsupportedAppUsage
+    protected long getLongInteger(int field) {
+        Long longInteger = (Long) mHeaderMap.get(field);
+        if (null == longInteger) {
+            return -1;
+        }
+
+        return longInteger.longValue();
+    }
+
+    /**
+     * Set LongInteger value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     */
+    @UnsupportedAppUsage
+    protected void setLongInteger(long value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case DATE:
+            case REPLY_CHARGING_SIZE:
+            case MESSAGE_SIZE:
+            case MESSAGE_COUNT:
+            case START:
+            case LIMIT:
+            case DELIVERY_TIME:
+            case EXPIRY:
+            case REPLY_CHARGING_DEADLINE:
+            case PREVIOUSLY_SENT_DATE:
+                break;
+            default:
+                // This header value should not be LongInteger.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduParser.java b/telephony/java/com/google/android/mms/pdu/PduParser.java
new file mode 100755
index 0000000..f483994
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduParser.java
@@ -0,0 +1,2023 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+    /**
+     *  The next are WAP values defined in WSP specification.
+     */
+    private static final int QUOTE = 127;
+    private static final int LENGTH_QUOTE = 31;
+    private static final int TEXT_MIN = 32;
+    private static final int TEXT_MAX = 127;
+    private static final int SHORT_INTEGER_MAX = 127;
+    private static final int SHORT_LENGTH_MAX = 30;
+    private static final int LONG_INTEGER_LENGTH_MAX = 8;
+    private static final int QUOTED_STRING_FLAG = 34;
+    private static final int END_STRING_FLAG = 0x00;
+    //The next two are used by the interface "parseWapString" to
+    //distinguish Text-String and Quoted-String.
+    private static final int TYPE_TEXT_STRING = 0;
+    private static final int TYPE_QUOTED_STRING = 1;
+    private static final int TYPE_TOKEN_STRING = 2;
+
+    /**
+     * Specify the part position.
+     */
+    private static final int THE_FIRST_PART = 0;
+    private static final int THE_LAST_PART = 1;
+
+    /**
+     * The pdu data.
+     */
+    private ByteArrayInputStream mPduDataStream = null;
+
+    /**
+     * Store pdu headers
+     */
+    private PduHeaders mHeaders = null;
+
+    /**
+     * Store pdu parts.
+     */
+    private PduBody mBody = null;
+
+    /**
+     * Store the "type" parameter in "Content-Type" header field.
+     */
+    private static byte[] mTypeParam = null;
+
+    /**
+     * Store the "start" parameter in "Content-Type" header field.
+     */
+    private static byte[] mStartParam = null;
+
+    /**
+     * The log tag.
+     */
+    private static final String LOG_TAG = "PduParser";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    /**
+     * Whether to parse content-disposition part header
+     */
+    private final boolean mParseContentDisposition;
+
+    /**
+     * Constructor.
+     *
+     * @param pduDataStream pdu data to be parsed
+     * @param parseContentDisposition whether to parse the Content-Disposition part header
+     */
+    @UnsupportedAppUsage
+    public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
+        mPduDataStream = new ByteArrayInputStream(pduDataStream);
+        mParseContentDisposition = parseContentDisposition;
+    }
+
+    /**
+     * Parse the pdu.
+     *
+     * @return the pdu structure if parsing successfully.
+     *         null if parsing error happened or mandatory fields are not set.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu parse(){
+        if (mPduDataStream == null) {
+            return null;
+        }
+
+        /* parse headers */
+        mHeaders = parseHeaders(mPduDataStream);
+        if (null == mHeaders) {
+            // Parse headers failed.
+            return null;
+        }
+
+        /* get the message type */
+        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check mandatory header fields */
+        if (false == checkMandatoryHeader(mHeaders)) {
+            log("check mandatory headers failed!");
+            return null;
+        }
+
+        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+            /* need to parse the parts */
+            mBody = parseParts(mPduDataStream);
+            if (null == mBody) {
+                // Parse parts failed.
+                return null;
+            }
+        }
+
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
+                }
+                SendReq sendReq = new SendReq(mHeaders, mBody);
+                return sendReq;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
+                }
+                SendConf sendConf = new SendConf(mHeaders);
+                return sendConf;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
+                }
+                NotificationInd notificationInd =
+                    new NotificationInd(mHeaders);
+                return notificationInd;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
+                }
+                NotifyRespInd notifyRespInd =
+                    new NotifyRespInd(mHeaders);
+                return notifyRespInd;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
+                }
+                RetrieveConf retrieveConf =
+                    new RetrieveConf(mHeaders, mBody);
+
+                byte[] contentType = retrieveConf.getContentType();
+                if (null == contentType) {
+                    return null;
+                }
+                String ctTypeStr = new String(contentType);
+                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // The MMS content type must be "application/vnd.wap.multipart.mixed"
+                    // or "application/vnd.wap.multipart.related"
+                    // or "application/vnd.wap.multipart.alternative"
+                    return retrieveConf;
+                } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // "application/vnd.wap.multipart.alternative"
+                    // should take only the first part.
+                    PduPart firstPart = mBody.getPart(0);
+                    mBody.removeAll();
+                    mBody.addPart(0, firstPart);
+                    return retrieveConf;
+                }
+                return null;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
+                }
+                DeliveryInd deliveryInd =
+                    new DeliveryInd(mHeaders);
+                return deliveryInd;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
+                }
+                AcknowledgeInd acknowledgeInd =
+                    new AcknowledgeInd(mHeaders);
+                return acknowledgeInd;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
+                }
+                ReadOrigInd readOrigInd =
+                    new ReadOrigInd(mHeaders);
+                return readOrigInd;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
+                }
+                ReadRecInd readRecInd =
+                    new ReadRecInd(mHeaders);
+                return readRecInd;
+            default:
+                log("Parser doesn't support this message type in this version!");
+            return null;
+        }
+    }
+
+    /**
+     * Parse pdu headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return headers in PduHeaders structure, null when parse fail
+     */
+    protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+        if (pduDataStream == null) {
+            return null;
+        }
+        boolean keepParsing = true;
+        PduHeaders headers = new PduHeaders();
+
+        while (keepParsing && (pduDataStream.available() > 0)) {
+            pduDataStream.mark(1);
+            int headerField = extractByteValue(pduDataStream);
+            /* parse custom text header */
+            if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+                pduDataStream.reset();
+                byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+                }
+                /* we should ignore it at the moment */
+                continue;
+            }
+            switch (headerField) {
+                case PduHeaders.MESSAGE_TYPE:
+                {
+                    int messageType = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType);
+                    }
+                    switch (messageType) {
+                        // We don't support these kind of messages now.
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                            return null;
+                    }
+                    try {
+                        headers.setOctet(messageType, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + messageType +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+                /* Octect value */
+                case PduHeaders.REPORT_ALLOWED:
+                case PduHeaders.ADAPTATION_ALLOWED:
+                case PduHeaders.DELIVERY_REPORT:
+                case PduHeaders.DRM_CONTENT:
+                case PduHeaders.DISTRIBUTION_INDICATOR:
+                case PduHeaders.QUOTAS:
+                case PduHeaders.READ_REPORT:
+                case PduHeaders.STORE:
+                case PduHeaders.STORED:
+                case PduHeaders.TOTALS:
+                case PduHeaders.SENDER_VISIBILITY:
+                case PduHeaders.READ_STATUS:
+                case PduHeaders.CANCEL_STATUS:
+                case PduHeaders.PRIORITY:
+                case PduHeaders.STATUS:
+                case PduHeaders.REPLY_CHARGING:
+                case PduHeaders.MM_STATE:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+                case PduHeaders.CONTENT_CLASS:
+                case PduHeaders.RETRIEVE_STATUS:
+                case PduHeaders.STORE_STATUS:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.RESPONSE_STATUS:
+                {
+                    int value = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " +
+                                value);
+                    }
+
+                    try {
+                        headers.setOctet(value, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + value +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Long-Integer */
+                case PduHeaders.DATE:
+                case PduHeaders.REPLY_CHARGING_SIZE:
+                case PduHeaders.MESSAGE_SIZE:
+                {
+                    try {
+                        long value = parseLongInteger(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " +
+                                    value);
+                        }
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Integer-Value */
+                case PduHeaders.MESSAGE_COUNT:
+                case PduHeaders.START:
+                case PduHeaders.LIMIT:
+                {
+                    try {
+                        long value = parseIntegerValue(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " +
+                                    value);
+                        }
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Text-String */
+                case PduHeaders.TRANSACTION_ID:
+                case PduHeaders.REPLY_CHARGING_ID:
+                case PduHeaders.AUX_APPLIC_ID:
+                case PduHeaders.APPLIC_ID:
+                case PduHeaders.REPLY_APPLIC_ID:
+                    /**
+                     * The next three header fields are email addresses
+                     * as defined in RFC2822,
+                     * not including the characters "<" and ">"
+                     */
+                case PduHeaders.MESSAGE_ID:
+                case PduHeaders.REPLACE_ID:
+                case PduHeaders.CANCEL_ID:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.CONTENT_LOCATION:
+                {
+                    byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if (null != value) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " +
+                                        new String(value));
+                            }
+                            headers.setTextString(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Encoded-string-value */
+                case PduHeaders.SUBJECT:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+                case PduHeaders.RETRIEVE_TEXT:
+                case PduHeaders.STATUS_TEXT:
+                case PduHeaders.STORE_STATUS_TEXT:
+                    /* the next one is not support
+                     * M-Mbox-Delete.conf and M-Delete.conf now */
+                case PduHeaders.RESPONSE_TEXT:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField
+                                        + " value: " + value.getString());
+                            }
+                            headers.setEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch (RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Addressing model */
+                case PduHeaders.BCC:
+                case PduHeaders.CC:
+                case PduHeaders.TO:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        byte[] address = value.getTextString();
+                        if (null != address) {
+                            String str = new String(address);
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
+                                        + " value: " + str);
+                            }
+                            int endIndex = str.indexOf("/");
+                            if (endIndex > 0) {
+                                str = str.substring(0, endIndex);
+                            }
+                            try {
+                                value.setTextString(str.getBytes());
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                                return null;
+                            }
+                        }
+
+                        try {
+                            headers.appendEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Value-length
+                 * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+                case PduHeaders.DELIVERY_TIME:
+                case PduHeaders.EXPIRY:
+                case PduHeaders.REPLY_CHARGING_DEADLINE:
+                {
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Absolute-token or Relative-token */
+                    int token = extractByteValue(pduDataStream);
+
+                    /* Date-value or Delta-seconds-value */
+                    long timeValue;
+                    try {
+                        timeValue = parseLongInteger(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+                        /* need to convert the Delta-seconds-value
+                         * into Date-value */
+                        timeValue = System.currentTimeMillis()/1000 + timeValue;
+                    }
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: time value: " + headerField
+                                    + " value: " + timeValue);
+                        }
+                        headers.setLongInteger(timeValue, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.FROM: {
+                    /* From-value =
+                     * Value-length
+                     * (Address-present-token Encoded-string-value | Insert-address-token)
+                     */
+                    EncodedStringValue from = null;
+                    parseValueLength(pduDataStream); /* parse value-length */
+
+                    /* Address-present-token or Insert-address-token */
+                    int fromToken = extractByteValue(pduDataStream);
+
+                    /* Address-present-token or Insert-address-token */
+                    if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+                        /* Encoded-string-value */
+                        from = parseEncodedStringValue(pduDataStream);
+                        if (null != from) {
+                            byte[] address = from.getTextString();
+                            if (null != address) {
+                                String str = new String(address);
+                                int endIndex = str.indexOf("/");
+                                if (endIndex > 0) {
+                                    str = str.substring(0, endIndex);
+                                }
+                                try {
+                                    from.setTextString(str.getBytes());
+                                } catch(NullPointerException e) {
+                                    log("null pointer error!");
+                                    return null;
+                                }
+                            }
+                        }
+                    } else {
+                        try {
+                            from = new EncodedStringValue(
+                                    PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+                        } catch(NullPointerException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: from address: " + headerField
+                                    + " value: " + from.getString());
+                        }
+                        headers.setEncodedStringValue(from, PduHeaders.FROM);
+                    } catch(NullPointerException e) {
+                        log("null pointer error!");
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Encoded-String-Value header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MESSAGE_CLASS: {
+                    /* Message-class-value = Class-identifier | Token-text */
+                    pduDataStream.mark(1);
+                    int messageClass = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField
+                                + " value: " + messageClass);
+                    }
+
+                    if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+                        /* Class-identifier */
+                        try {
+                            if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            }
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    } else {
+                        /* Token-text */
+                        pduDataStream.reset();
+                        byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != messageClassString) {
+                            try {
+                                headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                            } catch(RuntimeException e) {
+                                log(headerField + "is not Text-String header field!");
+                                return null;
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.MMS_VERSION: {
+                    int version = parseShortInteger(pduDataStream);
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField
+                                    + " value: " + version);
+                        }
+                        headers.setOctet(version, PduHeaders.MMS_VERSION);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + version +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_BY: {
+                    /* Previously-sent-by-value =
+                     * Value-length Forwarded-count-value Encoded-string-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* parse Encoded-string-value */
+                    EncodedStringValue previouslySentBy =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != previouslySentBy) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField
+                                        + " value: " + previouslySentBy.getString());
+                            }
+                            headers.setEncodedStringValue(previouslySentBy,
+                                    PduHeaders.PREVIOUSLY_SENT_BY);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_DATE: {
+                    /* Previously-sent-date-value =
+                     * Value-length Forwarded-count-value Date-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* Date-value */
+                    try {
+                        long perviouslySentDate = parseLongInteger(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField
+                                    + " value: " + perviouslySentDate);
+                        }
+                        headers.setLongInteger(perviouslySentDate,
+                                PduHeaders.PREVIOUSLY_SENT_DATE);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MM_FLAGS: {
+                    /* MM-flags-value =
+                     * Value-length
+                     * ( Add-token | Remove-token | Filter-token )
+                     * Encoded-string-value
+                     */
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField
+                                + " NOT REALLY SUPPORTED");
+                    }
+
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Add-token | Remove-token | Filter-token */
+                    extractByteValue(pduDataStream);
+
+                    /* Encoded-string-value */
+                    parseEncodedStringValue(pduDataStream);
+
+                    /* not store this header filed in "headers",
+                     * because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                /* Value-length
+                 * (Message-total-token | Size-total-token) Integer-Value */
+                case PduHeaders.MBOX_TOTALS:
+                case PduHeaders.MBOX_QUOTAS:
+                {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField);
+                    }
+                    /* Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Message-total-token | Size-total-token */
+                    extractByteValue(pduDataStream);
+
+                    /*Integer-Value*/
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* not store these headers filed in "headers",
+                    because now PduHeaders doesn't support them */
+                    break;
+                }
+
+                case PduHeaders.ELEMENT_DESCRIPTOR: {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField);
+                    }
+                    parseContentType(pduDataStream, null);
+
+                    /* not store this header filed in "headers",
+                    because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                case PduHeaders.CONTENT_TYPE: {
+                    HashMap<Integer, Object> map =
+                        new HashMap<Integer, Object>();
+                    byte[] contentType =
+                        parseContentType(pduDataStream, map);
+
+                    if (null != contentType) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField +
+                                        contentType.toString());
+                            }
+                            headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+
+                    /* get start parameter */
+                    mStartParam = (byte[]) map.get(PduPart.P_START);
+
+                    /* get charset parameter */
+                    mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+                    keepParsing = false;
+                    break;
+                }
+
+                case PduHeaders.CONTENT:
+                case PduHeaders.ADDITIONAL_HEADERS:
+                case PduHeaders.ATTRIBUTES:
+                default: {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField);
+                    }
+                    log("Unknown header");
+                }
+            }
+        }
+
+        return headers;
+    }
+
+    /**
+     * Parse pdu parts.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return parts in PduBody structure
+     */
+    protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        int count = parseUnsignedInt(pduDataStream); // get the number of parts
+        PduBody body = new PduBody();
+
+        for (int i = 0 ; i < count ; i++) {
+            int headerLength = parseUnsignedInt(pduDataStream);
+            int dataLength = parseUnsignedInt(pduDataStream);
+            PduPart part = new PduPart();
+            int startPos = pduDataStream.available();
+            if (startPos <= 0) {
+                // Invalid part.
+                return null;
+            }
+
+            /* parse part's content-type */
+            HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+            byte[] contentType = parseContentType(pduDataStream, map);
+            if (null != contentType) {
+                part.setContentType(contentType);
+            } else {
+                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+            }
+
+            /* get name parameter */
+            byte[] name = (byte[]) map.get(PduPart.P_NAME);
+            if (null != name) {
+                part.setName(name);
+            }
+
+            /* get charset parameter */
+            Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+            if (null != charset) {
+                part.setCharset(charset);
+            }
+
+            /* parse part's headers */
+            int endPos = pduDataStream.available();
+            int partHeaderLen = headerLength - (startPos - endPos);
+            if (partHeaderLen > 0) {
+                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+                    // Parse part header faild.
+                    return null;
+                }
+            } else if (partHeaderLen < 0) {
+                // Invalid length of content-type.
+                return null;
+            }
+
+            /* FIXME: check content-id, name, filename and content location,
+             * if not set anyone of them, generate a default content-location
+             */
+            if ((null == part.getContentLocation())
+                    && (null == part.getName())
+                    && (null == part.getFilename())
+                    && (null == part.getContentId())) {
+                part.setContentLocation(Long.toOctalString(
+                        System.currentTimeMillis()).getBytes());
+            }
+
+            /* get part's data */
+            if (dataLength > 0) {
+                byte[] partData = new byte[dataLength];
+                String partContentType = new String(part.getContentType());
+                pduDataStream.read(partData, 0, dataLength);
+                if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // parse "multipart/vnd.wap.multipart.alternative".
+                    PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+                    // take the first part of children.
+                    part = childBody.getPart(0);
+                } else {
+                    // Check Content-Transfer-Encoding.
+                    byte[] partDataEncoding = part.getContentTransferEncoding();
+                    if (null != partDataEncoding) {
+                        String encoding = new String(partDataEncoding);
+                        if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+                            // Decode "base64" into "binary".
+                            partData = Base64.decodeBase64(partData);
+                        } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+                            // Decode "quoted-printable" into "binary".
+                            partData = QuotedPrintable.decodeQuotedPrintable(partData);
+                        } else {
+                            // "binary" is the default encoding.
+                        }
+                    }
+                    if (null == partData) {
+                        log("Decode part data error!");
+                        return null;
+                    }
+                    part.setData(partData);
+                }
+            }
+
+            /* add this part to body */
+            if (THE_FIRST_PART == checkPartPosition(part)) {
+                /* this is the first part */
+                body.addPart(0, part);
+            } else {
+                /* add the part to the end */
+                body.addPart(part);
+            }
+        }
+
+        return body;
+    }
+
+    /**
+     * Log status.
+     *
+     * @param text log information
+     */
+    @UnsupportedAppUsage
+    private static void log(String text) {
+        if (LOCAL_LOGV) {
+            Log.v(LOG_TAG, text);
+        }
+    }
+
+    /**
+     * Parse unsigned integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer, -1 when failed
+     */
+    @UnsupportedAppUsage
+    protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * The maximum size of a uintvar is 32 bits.
+         * So it will be encoded in no more than 5 octets.
+         */
+        assert(null != pduDataStream);
+        int result = 0;
+        int temp = pduDataStream.read();
+        if (temp == -1) {
+            return temp;
+        }
+
+        while((temp & 0x80) != 0) {
+            result = result << 7;
+            result |= temp & 0x7F;
+            temp = pduDataStream.read();
+            if (temp == -1) {
+                return temp;
+            }
+        }
+
+        result = result << 7;
+        result |= temp & 0x7F;
+
+        return result;
+    }
+
+    /**
+     * Parse value length.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer
+     */
+    @UnsupportedAppUsage
+    protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Value-length = Short-length | (Length-quote Length)
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         * Uintvar-integer = 1*5 OCTET
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        if (first <= SHORT_LENGTH_MAX) {
+            return first;
+        } else if (first == LENGTH_QUOTE) {
+            return parseUnsignedInt(pduDataStream);
+        }
+
+        throw new RuntimeException ("Value length > LENGTH_QUOTE!");
+    }
+
+    /**
+     * Parse encoded string value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the EncodedStringValue
+     */
+    protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        EncodedStringValue returnValue = null;
+        int charset = 0;
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+        if (first == 0) {
+            return new EncodedStringValue("");
+        }
+
+        pduDataStream.reset();
+        if (first < TEXT_MIN) {
+            parseValueLength(pduDataStream);
+
+            charset = parseShortInteger(pduDataStream); //get the "Charset"
+        }
+
+        byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+        try {
+            if (0 != charset) {
+                returnValue = new EncodedStringValue(charset, textString);
+            } else {
+                returnValue = new EncodedStringValue(textString);
+            }
+        } catch(Exception e) {
+            return null;
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * Parse Text-String or Quoted-String.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+     * @return the string without End-of-string in byte array
+     */
+    @UnsupportedAppUsage
+    protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Text-string = [Quote] *TEXT End-of-string
+         * If the first character in the TEXT is in the range of 128-255,
+         * a Quote character must precede it.
+         * Otherwise the Quote character must be omitted.
+         * The Quote is not part of the contents.
+         * Quote = <Octet 127>
+         * End-of-string = <Octet 0>
+         *
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         *
+         * Token-text = Token End-of-string
+         */
+
+        // Mark supposed beginning of Text-string
+        // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+        pduDataStream.mark(1);
+
+        // Check first char
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        if ((TYPE_QUOTED_STRING == stringType) &&
+                (QUOTED_STRING_FLAG == temp)) {
+            // Mark again if QUOTED_STRING_FLAG and ignore it
+            pduDataStream.mark(1);
+        } else if ((TYPE_TEXT_STRING == stringType) &&
+                (QUOTE == temp)) {
+            // Mark again if QUOTE and ignore it
+            pduDataStream.mark(1);
+        } else {
+            // Otherwise go back to origin
+            pduDataStream.reset();
+        }
+
+        // We are now definitely at the beginning of string
+        /**
+         * Return *TOKEN or *TEXT (Text-String without QUOTE,
+         * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+         */
+        return getWapString(pduDataStream, stringType);
+    }
+
+    /**
+     * Check TOKEN data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TOKEN, false when ch is not TOKEN
+     */
+    protected static boolean isTokenCharacter(int ch) {
+        /**
+         * Token      = 1*<any CHAR except CTLs or separators>
+         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
+         * CHAR       = <any US-ASCII character (octets 0 - 127)>
+         * CTL        = <any US-ASCII control character
+         *            (octets 0 - 31) and DEL (127)>
+         * SP         = <US-ASCII SP, space (32)>
+         * HT         = <US-ASCII HT, horizontal-tab (9)>
+         */
+        if((ch < 33) || (ch > 126)) {
+            return false;
+        }
+
+        switch(ch) {
+            case '"': /* '"' */
+            case '(': /* '(' */
+            case ')': /* ')' */
+            case ',': /* ',' */
+            case '/': /* '/' */
+            case ':': /* ':' */
+            case ';': /* ';' */
+            case '<': /* '<' */
+            case '=': /* '=' */
+            case '>': /* '>' */
+            case '?': /* '?' */
+            case '@': /* '@' */
+            case '[': /* '[' */
+            case '\\': /* '\' */
+            case ']': /* ']' */
+            case '{': /* '{' */
+            case '}': /* '}' */
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check TEXT data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TEXT, false when ch is not TEXT
+     */
+    protected static boolean isText(int ch) {
+        /**
+         * TEXT = <any OCTET except CTLs,
+         *      but including LWS>
+         * CTL  = <any US-ASCII control character
+         *      (octets 0 - 31) and DEL (127)>
+         * LWS  = [CRLF] 1*( SP | HT )
+         * CRLF = CR LF
+         * CR   = <US-ASCII CR, carriage return (13)>
+         * LF   = <US-ASCII LF, linefeed (10)>
+         */
+        if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+            return true;
+        }
+
+        switch(ch) {
+            case '\t': /* '\t' */
+            case '\n': /* '\n' */
+            case '\r': /* '\r' */
+                return true;
+        }
+
+        return false;
+    }
+
+    protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        while((-1 != temp) && ('\0' != temp)) {
+            // check each of the character
+            if (stringType == TYPE_TOKEN_STRING) {
+                if (isTokenCharacter(temp)) {
+                    out.write(temp);
+                }
+            } else {
+                if (isText(temp)) {
+                    out.write(temp);
+                }
+            }
+
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+        }
+
+        if (out.size() > 0) {
+            return out.toByteArray();
+        }
+
+        return null;
+    }
+
+    /**
+     * Extract a byte value from the input stream.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0xFF;
+    }
+
+    /**
+     * Parse Short-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    @UnsupportedAppUsage
+    protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Short-integer = OCTET
+         * Integers in range 0-127 shall be encoded as a one
+         * octet value with the most significant bit set to one (1xxx xxxx)
+         * and with the value in the remaining least significant bits.
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0x7F;
+    }
+
+    /**
+     * Parse Long-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Long-integer = Short-length Multi-octet-integer
+         * The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * The content octets shall be an unsigned integer value
+         * with the most significant octet encoded first (big-endian representation).
+         * The minimum number of octets must be used to encode the value.
+         * Short-length = <Any octet 0-30>
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int count = temp & 0xFF;
+
+        if (count > LONG_INTEGER_LENGTH_MAX) {
+            throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+        }
+
+        long result = 0;
+
+        for (int i = 0 ; i < count ; i++) {
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            result <<= 8;
+            result += (temp & 0xFF);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse Integer-Value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Integer-Value = Short-integer | Long-integer
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+        if (temp > SHORT_INTEGER_MAX) {
+            return parseShortInteger(pduDataStream);
+        } else {
+            return parseLongInteger(pduDataStream);
+        }
+    }
+
+    /**
+     * To skip length of the wap value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param length area size
+     * @return the values in this area
+     */
+    protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+        assert(null != pduDataStream);
+        byte[] area = new byte[length];
+        int readLen = pduDataStream.read(area, 0, length);
+        if (readLen < length) { //The actually read length is lower than the length
+            return -1;
+        } else {
+            return readLen;
+        }
+    }
+
+    /**
+     * Parse content type parameters. For now we just support
+     * four parameters used in mms: "type", "start", "name", "charset".
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters of Content-Type field
+     * @param length length of all the parameters
+     */
+    protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map, Integer length) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Parameter = Typed-parameter | Untyped-parameter
+         * Typed-parameter = Well-known-parameter-token Typed-value
+         * the actual expected type of the value is implied by the well-known parameter
+         * Well-known-parameter-token = Integer-value
+         * the code values used for parameters are specified in the Assigned Numbers appendix
+         * Typed-value = Compact-value | Text-value
+         * In addition to the expected type, there may be no value.
+         * If the value cannot be encoded using the expected type, it shall be encoded as text.
+         * Compact-value = Integer-value |
+         * Date-value | Delta-seconds-value | Q-value | Version-value |
+         * Uri-value
+         * Untyped-parameter = Token-text Untyped-value
+         * the type of the value is unknown, but it shall be encoded as an integer,
+         * if that is possible.
+         * Untyped-value = Integer-value | Text-value
+         */
+        assert(null != pduDataStream);
+        assert(length > 0);
+
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int param = pduDataStream.read();
+            assert(-1 != param);
+            lastLen--;
+
+            switch (param) {
+                /**
+                 * From rfc2387, chapter 3.1
+                 * The type parameter must be specified and its value is the MIME media
+                 * type of the "root" body part. It permits a MIME user agent to
+                 * determine the content-type without reference to the enclosed body
+                 * part. If the value of the type parameter and the root body part's
+                 * content-type differ then the User Agent's behavior is undefined.
+                 *
+                 * From wap-230-wsp-20010705-a.pdf
+                 * type = Constrained-encoding
+                 * Constrained-encoding = Extension-Media | Short-integer
+                 * Extension-media = *TEXT End-of-string
+                 */
+                case PduPart.P_TYPE:
+                case PduPart.P_CT_MR_TYPE:
+                    pduDataStream.mark(1);
+                    int first = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    if (first > TEXT_MAX) {
+                        // Short-integer (well-known type)
+                        int index = parseShortInteger(pduDataStream);
+
+                        if (index < PduContentTypes.contentTypes.length) {
+                            byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+                            map.put(PduPart.P_TYPE, type);
+                        } else {
+                            //not support this type, ignore it.
+                        }
+                    } else {
+                        // Text-String (extension-media)
+                        byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if ((null != type) && (null != map)) {
+                            map.put(PduPart.P_TYPE, type);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+                     * Start Parameter Referring to Presentation
+                     *
+                     * From rfc2387, chapter 3.2
+                     * The start parameter, if given, is the content-ID of the compound
+                     * object's "root". If not present the "root" is the first body part in
+                     * the Multipart/Related entity. The "root" is the element the
+                     * applications processes first.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * start = Text-String
+                     */
+                case PduPart.P_START:
+                case PduPart.P_DEP_START:
+                    byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != start) && (null != map)) {
+                        map.put(PduPart.P_START, start);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * In creation, the character set SHALL be either us-ascii
+                     * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+                     * In retrieval, both us-ascii and utf-8 SHALL be supported.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * charset = Well-known-charset|Text-String
+                     * Well-known-charset = Any-charset | Integer-value
+                     * Both are encoded using values from Character Set
+                     * Assignments table in Assigned Numbers
+                     * Any-charset = <Octet 128>
+                     * Equivalent to the special RFC2616 charset value "*"
+                     */
+                case PduPart.P_CHARSET:
+                    pduDataStream.mark(1);
+                    int firstValue = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    //Check first char
+                    if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+                            (END_STRING_FLAG == firstValue)) {
+                        //Text-String (extension-charset)
+                        byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        try {
+                            int charsetInt = CharacterSets.getMibEnumValue(
+                                    new String(charsetStr));
+                            map.put(PduPart.P_CHARSET, charsetInt);
+                        } catch (UnsupportedEncodingException e) {
+                            // Not a well-known charset, use "*".
+                            Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+                            map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+                        }
+                    } else {
+                        //Well-known-charset
+                        int charset = (int) parseIntegerValue(pduDataStream);
+                        if (map != null) {
+                            map.put(PduPart.P_CHARSET, charset);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * A name for multipart object SHALL be encoded using name-parameter
+                     * for Content-Type header in WSP multipart headers.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * name = Text-String
+                     */
+                case PduPart.P_DEP_NAME:
+                case PduPart.P_NAME:
+                    byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != name) && (null != map)) {
+                        map.put(PduPart.P_NAME, name);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+                default:
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "Not supported Content-Type parameter");
+                    }
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Content-Type");
+                } else {
+                    lastLen = 0;
+                }
+                break;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Content-Type");
+        }
+    }
+
+    /**
+     * Parse content type.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters in Content-Type header field
+     * @return Content-Type value
+     */
+    @UnsupportedAppUsage
+    protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Content-type-value = Constrained-media | Content-general-form
+         * Content-general-form = Value-length Media-type
+         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+         */
+        assert(null != pduDataStream);
+
+        byte[] contentType = null;
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+
+        int cur = (temp & 0xFF);
+
+        if (cur < TEXT_MIN) {
+            int length = parseValueLength(pduDataStream);
+            int startPos = pduDataStream.available();
+            pduDataStream.mark(1);
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            pduDataStream.reset();
+            int first = (temp & 0xFF);
+
+            if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+                contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+            } else if (first > TEXT_MAX) {
+                int index = parseShortInteger(pduDataStream);
+
+                if (index < PduContentTypes.contentTypes.length) { //well-known type
+                    contentType = (PduContentTypes.contentTypes[index]).getBytes();
+                } else {
+                    pduDataStream.reset();
+                    contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                }
+            } else {
+                Log.e(LOG_TAG, "Corrupt content-type");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+
+            int endPos = pduDataStream.available();
+            int parameterLen = length - (startPos - endPos);
+            if (parameterLen > 0) {//have parameters
+                parseContentTypeParams(pduDataStream, map, parameterLen);
+            }
+
+            if (parameterLen < 0) {
+                Log.e(LOG_TAG, "Corrupt MMS message");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+        } else if (cur <= TEXT_MAX) {
+            contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+        } else {
+            contentType =
+                (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+        }
+
+        return contentType;
+    }
+
+    /**
+     * Parse part's headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param part to store the header informations of the part
+     * @param length length of the headers
+     * @return true if parse successfully, false otherwise
+     */
+    @UnsupportedAppUsage
+    protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+            PduPart part, int length) {
+        assert(null != pduDataStream);
+        assert(null != part);
+        assert(length > 0);
+
+        /**
+         * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+         * A name for multipart object SHALL be encoded using name-parameter
+         * for Content-Type header in WSP multipart headers.
+         * In decoding, name-parameter of Content-Type SHALL be used if available.
+         * If name-parameter of Content-Type is not available,
+         * filename parameter of Content-Disposition header SHALL be used if available.
+         * If neither name-parameter of Content-Type header nor filename parameter
+         * of Content-Disposition header is available,
+         * Content-Location header SHALL be used if available.
+         *
+         * Within SMIL part the reference to the media object parts SHALL use
+         * either Content-ID or Content-Location mechanism [RFC2557]
+         * and the corresponding WSP part headers in media object parts
+         * contain the corresponding definitions.
+         */
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int header = pduDataStream.read();
+            assert(-1 != header);
+            lastLen--;
+
+            if (header > TEXT_MAX) {
+                // Number assigned headers.
+                switch (header) {
+                    case PduPart.P_CONTENT_LOCATION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-location-value = Uri-value
+                         */
+                        byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != contentLocation) {
+                            part.setContentLocation(contentLocation);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_CONTENT_ID:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-ID-value = Quoted-string
+                         */
+                        byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+                        if (null != contentId) {
+                            part.setContentId(contentId);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_DEP_CONTENT_DISPOSITION:
+                    case PduPart.P_CONTENT_DISPOSITION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-disposition-value = Value-length Disposition *(Parameter)
+                         * Disposition = Form-data | Attachment | Inline | Token-text
+                         * Form-data = <Octet 128>
+                         * Attachment = <Octet 129>
+                         * Inline = <Octet 130>
+                         */
+
+                        /*
+                         * some carrier mmsc servers do not support content_disposition
+                         * field correctly
+                         */
+                        if (mParseContentDisposition) {
+                            int len = parseValueLength(pduDataStream);
+                            pduDataStream.mark(1);
+                            int thisStartPos = pduDataStream.available();
+                            int thisEndPos = 0;
+                            int value = pduDataStream.read();
+
+                            if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+                                part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+                            } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+                                part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+                            } else if (value == PduPart.P_DISPOSITION_INLINE) {
+                                part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+                            } else {
+                                pduDataStream.reset();
+                                /* Token-text */
+                                part.setContentDisposition(parseWapString(pduDataStream
+                                        , TYPE_TEXT_STRING));
+                            }
+
+                            /* get filename parameter and skip other parameters */
+                            thisEndPos = pduDataStream.available();
+                            if (thisStartPos - thisEndPos < len) {
+                                value = pduDataStream.read();
+                                if (value == PduPart.P_FILENAME) { //filename is text-string
+                                    part.setFilename(parseWapString(pduDataStream
+                                            , TYPE_TEXT_STRING));
+                                }
+
+                                /* skip other parameters */
+                                thisEndPos = pduDataStream.available();
+                                if (thisStartPos - thisEndPos < len) {
+                                    int last = len - (thisStartPos - thisEndPos);
+                                    byte[] temp = new byte[last];
+                                    pduDataStream.read(temp, 0, last);
+                                }
+                            }
+
+                            tempPos = pduDataStream.available();
+                            lastLen = length - (startPos - tempPos);
+                        }
+                        break;
+                    default:
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                        }
+                    if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                        Log.e(LOG_TAG, "Corrupt Part headers");
+                        return false;
+                    }
+                    lastLen = 0;
+                    break;
+                }
+            } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+                // Not assigned header.
+                byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+                // Check the header whether it is "Content-Transfer-Encoding".
+                if (true ==
+                    PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+                    part.setContentTransferEncoding(tempValue);
+                }
+
+                tempPos = pduDataStream.available();
+                lastLen = length - (startPos - tempPos);
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                }
+                // Skip all headers of this part.
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Part headers");
+                    return false;
+                }
+                lastLen = 0;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Part headers");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check the position of a specified part.
+     *
+     * @param part the part to be checked
+     * @return part position, THE_FIRST_PART when it's the
+     * first one, THE_LAST_PART when it's the last one.
+     */
+    @UnsupportedAppUsage
+    private static int checkPartPosition(PduPart part) {
+        assert(null != part);
+        if ((null == mTypeParam) &&
+                (null == mStartParam)) {
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-id */
+        if (null != mStartParam) {
+            byte[] contentId = part.getContentId();
+            if (null != contentId) {
+                if (true == Arrays.equals(mStartParam, contentId)) {
+                    return THE_FIRST_PART;
+                }
+            }
+            // This is not the first part, so append to end (keeping the original order)
+            // Check b/19607294 for details of this change
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-type */
+        if (null != mTypeParam) {
+            byte[] contentType = part.getContentType();
+            if (null != contentType) {
+                if (true == Arrays.equals(mTypeParam, contentType)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        return THE_LAST_PART;
+    }
+
+    /**
+     * Check mandatory headers of a pdu.
+     *
+     * @param headers pdu headers
+     * @return true if the pdu has all of the mandatory headers, false otherwise.
+     */
+    protected static boolean checkMandatoryHeader(PduHeaders headers) {
+        if (null == headers) {
+            return false;
+        }
+
+        /* get message type */
+        int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check Mms-Version field */
+        int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+        if (0 == mmsVersion) {
+            // Every message should have Mms-Version field.
+            return false;
+        }
+
+        /* check mandatory header fields */
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                // Content-Type field.
+                byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == srContentType) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == srFrom) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == srTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                // Response-Status field.
+                int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+                if (0 == scResponseStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == scTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                // Content-Location field.
+                byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+                if (null == niContentLocation) {
+                    return false;
+                }
+
+                // Expiry field.
+                long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+                if (-1 == niExpiry) {
+                    return false;
+                }
+
+                // Message-Class field.
+                byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+                if (null == niMessageClass) {
+                    return false;
+                }
+
+                // Message-Size field.
+                long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+                if (-1 == niMessageSize) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == niTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                // Status field.
+                int nriStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == nriStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == nriTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                // Content-Type field.
+                byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == rcContentType) {
+                    return false;
+                }
+
+                // Date field.
+                long rcDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == rcDate) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                // Date field.
+                long diDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == diDate) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == diMessageId) {
+                    return false;
+                }
+
+                // Status field.
+                int diStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == diStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == diTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                // Transaction-Id field.
+                byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == aiTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                // Date field.
+                long roDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == roDate) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == roFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == roMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == roReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == roTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                // From field.
+                EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == rrFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == rrMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == rrReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == rrTo) {
+                    return false;
+                }
+
+                break;
+            default:
+                // Parser doesn't support this message type in this version.
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduPart.java b/telephony/java/com/google/android/mms/pdu/PduPart.java
new file mode 100644
index 0000000..09b7751
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPart.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.net.Uri;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+    /**
+     * Well-Known Parameters.
+     */
+    public static final int P_Q                  = 0x80;
+    public static final int P_CHARSET            = 0x81;
+    public static final int P_LEVEL              = 0x82;
+    public static final int P_TYPE               = 0x83;
+    public static final int P_DEP_NAME           = 0x85;
+    public static final int P_DEP_FILENAME       = 0x86;
+    public static final int P_DIFFERENCES        = 0x87;
+    public static final int P_PADDING            = 0x88;
+    // This value of "TYPE" s used with Content-Type: multipart/related
+    public static final int P_CT_MR_TYPE         = 0x89;
+    public static final int P_DEP_START          = 0x8A;
+    public static final int P_DEP_START_INFO     = 0x8B;
+    public static final int P_DEP_COMMENT        = 0x8C;
+    public static final int P_DEP_DOMAIN         = 0x8D;
+    public static final int P_MAX_AGE            = 0x8E;
+    public static final int P_DEP_PATH           = 0x8F;
+    public static final int P_SECURE             = 0x90;
+    public static final int P_SEC                = 0x91;
+    public static final int P_MAC                = 0x92;
+    public static final int P_CREATION_DATE      = 0x93;
+    public static final int P_MODIFICATION_DATE  = 0x94;
+    public static final int P_READ_DATE          = 0x95;
+    public static final int P_SIZE               = 0x96;
+    public static final int P_NAME               = 0x97;
+    public static final int P_FILENAME           = 0x98;
+    public static final int P_START              = 0x99;
+    public static final int P_START_INFO         = 0x9A;
+    public static final int P_COMMENT            = 0x9B;
+    public static final int P_DOMAIN             = 0x9C;
+    public static final int P_PATH               = 0x9D;
+
+    /**
+     *  Header field names.
+     */
+     public static final int P_CONTENT_TYPE       = 0x91;
+     public static final int P_CONTENT_LOCATION   = 0x8E;
+     public static final int P_CONTENT_ID         = 0xC0;
+     public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+     public static final int P_CONTENT_DISPOSITION = 0xC5;
+    // The next header is unassigned header, use reserved header(0x48) value.
+     public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+     /**
+      * Content=Transfer-Encoding string.
+      */
+     public static final String CONTENT_TRANSFER_ENCODING =
+             "Content-Transfer-Encoding";
+
+     /**
+      * Value of Content-Transfer-Encoding.
+      */
+     public static final String P_BINARY = "binary";
+     public static final String P_7BIT = "7bit";
+     public static final String P_8BIT = "8bit";
+     public static final String P_BASE64 = "base64";
+     public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+     /**
+      * Value of disposition can be set to PduPart when the value is octet in
+      * the PDU.
+      * "from-data" instead of Form-data<Octet 128>.
+      * "attachment" instead of Attachment<Octet 129>.
+      * "inline" instead of Inline<Octet 130>.
+      */
+     static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+     static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+     static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+     /**
+      * Content-Disposition value.
+      */
+     public static final int P_DISPOSITION_FROM_DATA  = 0x80;
+     public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+     public static final int P_DISPOSITION_INLINE     = 0x82;
+
+     /**
+      * Header of part.
+      */
+     private Map<Integer, Object> mPartHeader = null;
+
+     /**
+      * Data uri.
+      */
+     private Uri mUri = null;
+
+     /**
+      * Part data.
+      */
+     private byte[] mPartData = null;
+
+     private static final String TAG = "PduPart";
+
+     /**
+      * Empty Constructor.
+      */
+     @UnsupportedAppUsage
+     public PduPart() {
+         mPartHeader = new HashMap<Integer, Object>();
+     }
+
+     /**
+      * Set part data. The data are stored as byte array.
+      *
+      * @param data the data
+      */
+     @UnsupportedAppUsage
+     public void setData(byte[] data) {
+         if(data == null) {
+            return;
+        }
+
+         mPartData = new byte[data.length];
+         System.arraycopy(data, 0, mPartData, 0, data.length);
+     }
+
+     /**
+      * @return A copy of the part data or null if the data wasn't set or
+      *         the data is stored as Uri.
+      * @see #getDataUri
+      */
+     @UnsupportedAppUsage
+     public byte[] getData() {
+         if(mPartData == null) {
+            return null;
+         }
+
+         byte[] byteArray = new byte[mPartData.length];
+         System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+         return byteArray;
+     }
+
+    /**
+     * @return The length of the data, if this object have data, else 0.
+     */
+     @UnsupportedAppUsage
+     public int getDataLength() {
+         if(mPartData != null){
+             return mPartData.length;
+         } else {
+             return 0;
+         }
+     }
+
+
+     /**
+      * Set data uri. The data are stored as Uri.
+      *
+      * @param uri the uri
+      */
+     @UnsupportedAppUsage
+     public void setDataUri(Uri uri) {
+         mUri = uri;
+     }
+
+     /**
+      * @return The Uri of the part data or null if the data wasn't set or
+      *         the data is stored as byte array.
+      * @see #getData
+      */
+     @UnsupportedAppUsage
+     public Uri getDataUri() {
+         return mUri;
+     }
+
+     /**
+      * Set Content-id value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentId(byte[] contentId) {
+         if((contentId == null) || (contentId.length == 0)) {
+             throw new IllegalArgumentException(
+                     "Content-Id may not be null or empty.");
+         }
+
+         if ((contentId.length > 1)
+                 && ((char) contentId[0] == '<')
+                 && ((char) contentId[contentId.length - 1] == '>')) {
+             mPartHeader.put(P_CONTENT_ID, contentId);
+             return;
+         }
+
+         // Insert beginning '<' and trailing '>' for Content-Id.
+         byte[] buffer = new byte[contentId.length + 2];
+         buffer[0] = (byte) (0xff & '<');
+         buffer[buffer.length - 1] = (byte) (0xff & '>');
+         System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+         mPartHeader.put(P_CONTENT_ID, buffer);
+     }
+
+     /**
+      * Get Content-id value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentId() {
+         return (byte[]) mPartHeader.get(P_CONTENT_ID);
+     }
+
+     /**
+      * Set Char-set value.
+      *
+      * @param charset the value
+      */
+     @UnsupportedAppUsage
+     public void setCharset(int charset) {
+         mPartHeader.put(P_CHARSET, charset);
+     }
+
+     /**
+      * Get Char-set value
+      *
+      * @return the charset value. Return 0 if charset was not set.
+      */
+     @UnsupportedAppUsage
+     public int getCharset() {
+         Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+         if(charset == null) {
+             return 0;
+         } else {
+             return charset.intValue();
+         }
+     }
+
+     /**
+      * Set Content-Location value.
+      *
+      * @param contentLocation the value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentLocation(byte[] contentLocation) {
+         if(contentLocation == null) {
+             throw new NullPointerException("null content-location");
+         }
+
+         mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+     }
+
+     /**
+      * Get Content-Location value.
+      *
+      * @return the value
+      *     return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      *     return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      *     return PduPart.disposition[2] instead of <Octet 130> (Inline).
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentLocation() {
+         return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+     }
+
+     /**
+      * Set Content-Disposition value.
+      * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+      *
+      * @param contentDisposition the value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentDisposition(byte[] contentDisposition) {
+         if(contentDisposition == null) {
+             throw new NullPointerException("null content-disposition");
+         }
+
+         mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+     }
+
+     /**
+      * Get Content-Disposition value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentDisposition() {
+         return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+     }
+
+     /**
+      *  Set Content-Type value.
+      *
+      *  @param value the value
+      *  @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentType(byte[] contentType) {
+         if(contentType == null) {
+             throw new NullPointerException("null content-type");
+         }
+
+         mPartHeader.put(P_CONTENT_TYPE, contentType);
+     }
+
+     /**
+      * Get Content-Type value of part.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentType() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+     }
+
+     /**
+      * Set Content-Transfer-Encoding value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+         if(contentTransferEncoding == null) {
+             throw new NullPointerException("null content-transfer-encoding");
+         }
+
+         mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+     }
+
+     /**
+      * Get Content-Transfer-Encoding value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentTransferEncoding() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+     }
+
+     /**
+      * Set Content-type parameter: name.
+      *
+      * @param name the name value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setName(byte[] name) {
+         if(null == name) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_NAME, name);
+     }
+
+     /**
+      *  Get content-type parameter: name.
+      *
+      *  @return the name
+      */
+     @UnsupportedAppUsage
+     public byte[] getName() {
+         return (byte[]) mPartHeader.get(P_NAME);
+     }
+
+     /**
+      * Get Content-disposition parameter: filename
+      *
+      * @param fileName the filename value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setFilename(byte[] fileName) {
+         if(null == fileName) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_FILENAME, fileName);
+     }
+
+     /**
+      * Set Content-disposition parameter: filename
+      *
+      * @return the filename
+      */
+     @UnsupportedAppUsage
+     public byte[] getFilename() {
+         return (byte[]) mPartHeader.get(P_FILENAME);
+     }
+
+    @UnsupportedAppUsage
+    public String generateLocation() {
+        // Assumption: At least one of the content-location / name / filename
+        // or content-id should be set. This is guaranteed by the PduParser
+        // for incoming messages and by MM composer for outgoing messages.
+        byte[] location = (byte[]) mPartHeader.get(P_NAME);
+        if(null == location) {
+            location = (byte[]) mPartHeader.get(P_FILENAME);
+
+            if (null == location) {
+                location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+            }
+        }
+
+        if (null == location) {
+            byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+            return "cid:" + new String(contentId);
+        } else {
+            return new String(location);
+        }
+    }
+}
+
diff --git a/telephony/java/com/google/android/mms/pdu/PduPersister.java b/telephony/java/com/google/android/mms/pdu/PduPersister.java
new file mode 100755
index 0000000..93f3065
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPersister.java
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.drm.DrmManagerClient;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.util.DownloadDrmHelper;
+import com.google.android.mms.util.DrmConvertSession;
+import com.google.android.mms.util.PduCache;
+import com.google.android.mms.util.PduCacheEntry;
+import com.google.android.mms.util.SqliteWrapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+    private static final String TAG = "PduPersister";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
+
+    /**
+     * The uri of temporary drm objects.
+     */
+    public static final String TEMPORARY_DRM_OBJECT_URI =
+        "content://mms/" + Long.MAX_VALUE + "/part";
+    /**
+     * Indicate that we transiently failed to process a MM.
+     */
+    public static final int PROC_STATUS_TRANSIENT_FAILURE   = 1;
+    /**
+     * Indicate that we permanently failed to process a MM.
+     */
+    public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+    /**
+     * Indicate that we have successfully processed a MM.
+     */
+    public static final int PROC_STATUS_COMPLETED           = 3;
+
+    private static PduPersister sPersister;
+    @UnsupportedAppUsage
+    private static final PduCache PDU_CACHE_INSTANCE;
+
+    @UnsupportedAppUsage
+    private static final int[] ADDRESS_FIELDS = new int[] {
+            PduHeaders.BCC,
+            PduHeaders.CC,
+            PduHeaders.FROM,
+            PduHeaders.TO
+    };
+
+    private static final String[] PDU_PROJECTION = new String[] {
+        Mms._ID,
+        Mms.MESSAGE_BOX,
+        Mms.THREAD_ID,
+        Mms.RETRIEVE_TEXT,
+        Mms.SUBJECT,
+        Mms.CONTENT_LOCATION,
+        Mms.CONTENT_TYPE,
+        Mms.MESSAGE_CLASS,
+        Mms.MESSAGE_ID,
+        Mms.RESPONSE_TEXT,
+        Mms.TRANSACTION_ID,
+        Mms.CONTENT_CLASS,
+        Mms.DELIVERY_REPORT,
+        Mms.MESSAGE_TYPE,
+        Mms.MMS_VERSION,
+        Mms.PRIORITY,
+        Mms.READ_REPORT,
+        Mms.READ_STATUS,
+        Mms.REPORT_ALLOWED,
+        Mms.RETRIEVE_STATUS,
+        Mms.STATUS,
+        Mms.DATE,
+        Mms.DELIVERY_TIME,
+        Mms.EXPIRY,
+        Mms.MESSAGE_SIZE,
+        Mms.SUBJECT_CHARSET,
+        Mms.RETRIEVE_TEXT_CHARSET,
+    };
+
+    private static final int PDU_COLUMN_ID                    = 0;
+    private static final int PDU_COLUMN_MESSAGE_BOX           = 1;
+    private static final int PDU_COLUMN_THREAD_ID             = 2;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
+    private static final int PDU_COLUMN_SUBJECT               = 4;
+    private static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
+    private static final int PDU_COLUMN_CONTENT_TYPE          = 6;
+    private static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
+    private static final int PDU_COLUMN_MESSAGE_ID            = 8;
+    private static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
+    private static final int PDU_COLUMN_TRANSACTION_ID        = 10;
+    private static final int PDU_COLUMN_CONTENT_CLASS         = 11;
+    private static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
+    private static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
+    private static final int PDU_COLUMN_MMS_VERSION           = 14;
+    private static final int PDU_COLUMN_PRIORITY              = 15;
+    private static final int PDU_COLUMN_READ_REPORT           = 16;
+    private static final int PDU_COLUMN_READ_STATUS           = 17;
+    private static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
+    private static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
+    private static final int PDU_COLUMN_STATUS                = 20;
+    private static final int PDU_COLUMN_DATE                  = 21;
+    private static final int PDU_COLUMN_DELIVERY_TIME         = 22;
+    private static final int PDU_COLUMN_EXPIRY                = 23;
+    private static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
+    private static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+
+    @UnsupportedAppUsage
+    private static final String[] PART_PROJECTION = new String[] {
+        Part._ID,
+        Part.CHARSET,
+        Part.CONTENT_DISPOSITION,
+        Part.CONTENT_ID,
+        Part.CONTENT_LOCATION,
+        Part.CONTENT_TYPE,
+        Part.FILENAME,
+        Part.NAME,
+        Part.TEXT
+    };
+
+    private static final int PART_COLUMN_ID                  = 0;
+    private static final int PART_COLUMN_CHARSET             = 1;
+    private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+    private static final int PART_COLUMN_CONTENT_ID          = 3;
+    private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
+    private static final int PART_COLUMN_CONTENT_TYPE        = 5;
+    private static final int PART_COLUMN_FILENAME            = 6;
+    private static final int PART_COLUMN_NAME                = 7;
+    private static final int PART_COLUMN_TEXT                = 8;
+
+    @UnsupportedAppUsage
+    private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
+    // These map are used for convenience in persist() and load().
+    private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
+
+    static {
+        MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
+        MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
+        MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
+        MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
+        MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+        CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+        CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+        // Encoded string field code -> column index/name map.
+        ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+        ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+        // Text string field code -> column index/name map.
+        TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+        TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+        // Octet field code -> column index/name map.
+        OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+        OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+        // Long field code -> column index/name map.
+        LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+        LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+        PDU_CACHE_INSTANCE = PduCache.getInstance();
+     }
+
+    @UnsupportedAppUsage
+    private final Context mContext;
+    @UnsupportedAppUsage
+    private final ContentResolver mContentResolver;
+    private final DrmManagerClient mDrmManagerClient;
+    @UnsupportedAppUsage
+    private final TelephonyManager mTelephonyManager;
+
+    private PduPersister(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        mDrmManagerClient = new DrmManagerClient(context);
+        mTelephonyManager = (TelephonyManager)context
+                .getSystemService(Context.TELEPHONY_SERVICE);
+     }
+
+    /** Get(or create if not exist) an instance of PduPersister */
+    @UnsupportedAppUsage
+    public static PduPersister getPduPersister(Context context) {
+        if ((sPersister == null)) {
+            sPersister = new PduPersister(context);
+        } else if (!context.equals(sPersister.mContext)) {
+            sPersister.release();
+            sPersister = new PduPersister(context);
+        }
+
+        return sPersister;
+    }
+
+    private void setEncodedStringValueToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if ((s != null) && (s.length() > 0)) {
+            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+            int charset = c.getInt(charsetColumnIndex);
+            EncodedStringValue value = new EncodedStringValue(
+                    charset, getBytes(s));
+            headers.setEncodedStringValue(value, mapColumn);
+        }
+    }
+
+    private void setTextStringToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if (s != null) {
+            headers.setTextString(getBytes(s), mapColumn);
+        }
+    }
+
+    private void setOctetToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
+        if (!c.isNull(columnIndex)) {
+            int b = c.getInt(columnIndex);
+            headers.setOctet(b, mapColumn);
+        }
+    }
+
+    private void setLongToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        if (!c.isNull(columnIndex)) {
+            long l = c.getLong(columnIndex);
+            headers.setLongInteger(l, mapColumn);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return c.getInt(columnIndex);
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return getBytes(c.getString(columnIndex));
+        }
+        return null;
+    }
+
+    private PduPart[] loadParts(long msgId) throws MmsException {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/part"),
+                PART_PROJECTION, null, null, null);
+
+        PduPart[] parts = null;
+
+        try {
+            if ((c == null) || (c.getCount() == 0)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
+                }
+                return null;
+            }
+
+            int partCount = c.getCount();
+            int partIdx = 0;
+            parts = new PduPart[partCount];
+            while (c.moveToNext()) {
+                PduPart part = new PduPart();
+                Integer charset = getIntegerFromPartColumn(
+                        c, PART_COLUMN_CHARSET);
+                if (charset != null) {
+                    part.setCharset(charset);
+                }
+
+                byte[] contentDisposition = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_DISPOSITION);
+                if (contentDisposition != null) {
+                    part.setContentDisposition(contentDisposition);
+                }
+
+                byte[] contentId = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_ID);
+                if (contentId != null) {
+                    part.setContentId(contentId);
+                }
+
+                byte[] contentLocation = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_LOCATION);
+                if (contentLocation != null) {
+                    part.setContentLocation(contentLocation);
+                }
+
+                byte[] contentType = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_TYPE);
+                if (contentType != null) {
+                    part.setContentType(contentType);
+                } else {
+                    throw new MmsException("Content-Type must be set.");
+                }
+
+                byte[] fileName = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_FILENAME);
+                if (fileName != null) {
+                    part.setFilename(fileName);
+                }
+
+                byte[] name = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_NAME);
+                if (name != null) {
+                    part.setName(name);
+                }
+
+                // Construct a Uri for this part.
+                long partId = c.getLong(PART_COLUMN_ID);
+                Uri partURI = Uri.parse("content://mms/part/" + partId);
+                part.setDataUri(partURI);
+
+                // For images/audio/video, we won't keep their data in Part
+                // because their renderer accept Uri as source.
+                String type = toIsoString(contentType);
+                if (!ContentType.isImageType(type)
+                        && !ContentType.isAudioType(type)
+                        && !ContentType.isVideoType(type)) {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream is = null;
+
+                    // Store simple string values directly in the database instead of an
+                    // external file.  This makes the text searchable and retrieval slightly
+                    // faster.
+                    if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
+                            || ContentType.TEXT_HTML.equals(type)) {
+                        String text = c.getString(PART_COLUMN_TEXT);
+                        byte [] blob = new EncodedStringValue(text != null ? text : "")
+                            .getTextString();
+                        baos.write(blob, 0, blob.length);
+                    } else {
+
+                        try {
+                            is = mContentResolver.openInputStream(partURI);
+
+                            byte[] buffer = new byte[256];
+                            int len = is.read(buffer);
+                            while (len >= 0) {
+                                baos.write(buffer, 0, len);
+                                len = is.read(buffer);
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to load part data", e);
+                            c.close();
+                            throw new MmsException(e);
+                        } finally {
+                            if (is != null) {
+                                try {
+                                    is.close();
+                                } catch (IOException e) {
+                                    Log.e(TAG, "Failed to close stream", e);
+                                } // Ignore
+                            }
+                        }
+                    }
+                    part.setData(baos.toByteArray());
+                }
+                parts[partIdx++] = part;
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        return parts;
+    }
+
+    private void loadAddress(long msgId, PduHeaders headers) {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
+                null, null, null);
+
+        if (c != null) {
+            try {
+                while (c.moveToNext()) {
+                    String addr = c.getString(0);
+                    if (!TextUtils.isEmpty(addr)) {
+                        int addrType = c.getInt(2);
+                        switch (addrType) {
+                            case PduHeaders.FROM:
+                                headers.setEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            case PduHeaders.TO:
+                            case PduHeaders.CC:
+                            case PduHeaders.BCC:
+                                headers.appendEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            default:
+                                Log.e(TAG, "Unknown address type: " + addrType);
+                                break;
+                        }
+                    }
+                }
+            } finally {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Load a PDU from storage by given Uri.
+     *
+     * @param uri The Uri of the PDU to be loaded.
+     * @return A generic PDU object, it may be cast to dedicated PDU.
+     * @throws MmsException Failed to load some fields of a PDU.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu load(Uri uri) throws MmsException {
+        GenericPdu pdu = null;
+        PduCacheEntry cacheEntry = null;
+        int msgBox = 0;
+        long threadId = -1;
+        try {
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "load: " + uri + " blocked by isUpdating()");
+                    }
+                    try {
+                        PDU_CACHE_INSTANCE.wait();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "load: ", e);
+                    }
+                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+                    if (cacheEntry != null) {
+                        return cacheEntry.getPdu();
+                    }
+                }
+                // Tell the cache to indicate to other callers that this item
+                // is currently being updated.
+                PDU_CACHE_INSTANCE.setUpdating(uri, true);
+            }
+
+            Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+                    PDU_PROJECTION, null, null, null);
+            PduHeaders headers = new PduHeaders();
+            Set<Entry<Integer, Integer>> set;
+            long msgId = ContentUris.parseId(uri);
+
+            try {
+                if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+                    throw new MmsException("Bad uri: " + uri);
+                }
+
+                msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+                threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+
+                set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setEncodedStringValueToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setTextStringToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = OCTET_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setOctetToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = LONG_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setLongToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+
+            // Check whether 'msgId' has been assigned a valid value.
+            if (msgId == -1L) {
+                throw new MmsException("Error! ID of the message: -1.");
+            }
+
+            // Load address information of the MM.
+            loadAddress(msgId, headers);
+
+            int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+            PduBody body = new PduBody();
+
+            // For PDU which type is M_retrieve.conf or Send.req, we should
+            // load multiparts and put them into the body of the PDU.
+            if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                    || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+                PduPart[] parts = loadParts(msgId);
+                if (parts != null) {
+                    int partsNum = parts.length;
+                    for (int i = 0; i < partsNum; i++) {
+                        body.addPart(parts[i]);
+                    }
+                }
+            }
+
+            switch (msgType) {
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                pdu = new NotificationInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                pdu = new DeliveryInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                pdu = new ReadOrigInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                pdu = new RetrieveConf(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                pdu = new SendReq(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                pdu = new AcknowledgeInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                pdu = new NotifyRespInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                pdu = new ReadRecInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                throw new MmsException(
+                        "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+            default:
+                throw new MmsException(
+                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
+            }
+        } finally {
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (pdu != null) {
+                    assert(PDU_CACHE_INSTANCE.get(uri) == null);
+                    // Update the cache entry with the real info
+                    cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+                    PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+                }
+                PDU_CACHE_INSTANCE.setUpdating(uri, false);
+                PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
+            }
+        }
+        return pdu;
+    }
+
+    @UnsupportedAppUsage
+    private void persistAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        ContentValues values = new ContentValues(3);
+
+        for (EncodedStringValue addr : array) {
+            values.clear(); // Clear all values first.
+            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+            values.put(Addr.CHARSET, addr.getCharacterSet());
+            values.put(Addr.TYPE, type);
+
+            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private static String getPartContentType(PduPart part) {
+        return part.getContentType() == null ? null : toIsoString(part.getContentType());
+    }
+
+    @UnsupportedAppUsage
+    public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+        ContentValues values = new ContentValues(8);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = getPartContentType(part);
+        if (contentType != null) {
+            // There is no "image/jpg" in Android (and it's an invalid mimetype).
+            // Change it to "image/jpeg"
+            if (ContentType.IMAGE_JPG.equals(contentType)) {
+                contentType = ContentType.IMAGE_JPEG;
+            }
+
+            values.put(Part.CONTENT_TYPE, contentType);
+            // To ensure the SMIL part is always the first part.
+            if (ContentType.APP_SMIL.equals(contentType)) {
+                values.put(Part.SEQ, -1);
+            }
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("Failed to persist part, return null.");
+        }
+
+        persistData(part, res, contentType, preOpenedFiles);
+        // After successfully store the data, we should update
+        // the dataUri of the part.
+        part.setDataUri(res);
+
+        return res;
+    }
+
+    /**
+     * Save data of the part into storage. The source data may be given
+     * by a byte[] or a Uri. If it's a byte[], directly save it
+     * into storage, otherwise load source data from the dataUri and then
+     * save it. If the data is an image, we may scale down it according
+     * to user preference.
+     *
+     * @param part The PDU part which contains data to be saved.
+     * @param uri The URI of the part.
+     * @param contentType The MIME type of the part.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @throws MmsException Cannot find source data or error occurred
+     *         while saving the data.
+     */
+    private void persistData(PduPart part, Uri uri,
+            String contentType, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        OutputStream os = null;
+        InputStream is = null;
+        DrmConvertSession drmConvertSession = null;
+        Uri dataUri = null;
+        String path = null;
+
+        try {
+            byte[] data = part.getData();
+            if (ContentType.TEXT_PLAIN.equals(contentType)
+                    || ContentType.APP_SMIL.equals(contentType)
+                    || ContentType.TEXT_HTML.equals(contentType)) {
+                ContentValues cv = new ContentValues();
+                if (data == null) {
+                    data = new String("").getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+                }
+                cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString());
+                if (mContentResolver.update(uri, cv, null, null) != 1) {
+                    throw new MmsException("unable to update " + uri.toString());
+                }
+            } else {
+                boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
+                if (isDrm) {
+                    if (uri != null) {
+                        try (ParcelFileDescriptor pfd =
+                                mContentResolver.openFileDescriptor(uri, "r")) {
+                            if (pfd.getStatSize() > 0) {
+                                // we're not going to re-persist and re-encrypt an already
+                                // converted drm file
+                                return;
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
+                        }
+                    }
+                    // We haven't converted the file yet, start the conversion
+                    drmConvertSession = DrmConvertSession.open(mContext, contentType);
+                    if (drmConvertSession == null) {
+                        throw new MmsException("Mimetype " + contentType +
+                                " can not be converted.");
+                    }
+                }
+                // uri can look like:
+                // content://mms/part/98
+                os = mContentResolver.openOutputStream(uri);
+                if (data == null) {
+                    dataUri = part.getDataUri();
+                    if ((dataUri == null) || (dataUri.equals(uri))) {
+                        Log.w(TAG, "Can't find data for this part.");
+                        return;
+                    }
+                    // dataUri can look like:
+                    // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715586
+                    if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
+                        is = preOpenedFiles.get(dataUri);
+                    }
+                    if (is == null) {
+                        is = mContentResolver.openInputStream(dataUri);
+                    }
+
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+
+                    byte[] buffer = new byte[8192];
+                    for (int len = 0; (len = is.read(buffer)) != -1; ) {
+                        if (!isDrm) {
+                            os.write(buffer, 0, len);
+                        } else {
+                            byte[] convertedData = drmConvertSession.convert(buffer, len);
+                            if (convertedData != null) {
+                                os.write(convertedData, 0, convertedData.length);
+                            } else {
+                                throw new MmsException("Error converting drm data.");
+                            }
+                        }
+                    }
+                } else {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+                    if (!isDrm) {
+                        os.write(data);
+                    } else {
+                        dataUri = uri;
+                        byte[] convertedData = drmConvertSession.convert(data, data.length);
+                        if (convertedData != null) {
+                            os.write(convertedData, 0, convertedData.length);
+                        } else {
+                            throw new MmsException("Error converting drm data.");
+                        }
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to open Input/Output stream.", e);
+            throw new MmsException(e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read/write data.", e);
+            throw new MmsException(e);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + os, e);
+                } // Ignore
+            }
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + is, e);
+                } // Ignore
+            }
+            if (drmConvertSession != null) {
+                drmConvertSession.close(path);
+
+                // Reset the permissions on the encrypted part file so everyone has only read
+                // permission.
+                File f = new File(path);
+                ContentValues values = new ContentValues(0);
+                SqliteWrapper.update(mContext, mContentResolver,
+                                     Uri.parse("content://mms/resetFilePerm/" + f.getName()),
+                                     values, null, null);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    private void updateAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        // Delete old address information and then insert new ones.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                Addr.TYPE + "=" + type, null);
+
+        persistAddress(msgId, type, array);
+    }
+
+    /**
+     * Update headers of a SendReq.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param pdu New headers.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    @UnsupportedAppUsage
+    public void updateHeaders(Uri uri, SendReq sendReq) {
+        synchronized(PDU_CACHE_INSTANCE) {
+            // If the cache item is getting updated, wait until it's done updating before
+            // purging it.
+            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
+                }
+                try {
+                    PDU_CACHE_INSTANCE.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "updateHeaders: ", e);
+                }
+            }
+        }
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        ContentValues values = new ContentValues(10);
+        byte[] contentType = sendReq.getContentType();
+        if (contentType != null) {
+            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+        }
+
+        long date = sendReq.getDate();
+        if (date != -1) {
+            values.put(Mms.DATE, date);
+        }
+
+        int deliveryReport = sendReq.getDeliveryReport();
+        if (deliveryReport != 0) {
+            values.put(Mms.DELIVERY_REPORT, deliveryReport);
+        }
+
+        long expiry = sendReq.getExpiry();
+        if (expiry != -1) {
+            values.put(Mms.EXPIRY, expiry);
+        }
+
+        byte[] msgClass = sendReq.getMessageClass();
+        if (msgClass != null) {
+            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+        }
+
+        int priority = sendReq.getPriority();
+        if (priority != 0) {
+            values.put(Mms.PRIORITY, priority);
+        }
+
+        int readReport = sendReq.getReadReport();
+        if (readReport != 0) {
+            values.put(Mms.READ_REPORT, readReport);
+        }
+
+        byte[] transId = sendReq.getTransactionId();
+        if (transId != null) {
+            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+        }
+
+        EncodedStringValue subject = sendReq.getSubject();
+        if (subject != null) {
+            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+        } else {
+            values.put(Mms.SUBJECT, "");
+        }
+
+        long messageSize = sendReq.getMessageSize();
+        if (messageSize > 0) {
+            values.put(Mms.MESSAGE_SIZE, messageSize);
+        }
+
+        PduHeaders headers = sendReq.getPduHeaders();
+        HashSet<String> recipients = new HashSet<String>();
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = headers.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = headers.getEncodedStringValues(addrType);
+            }
+
+            if (array != null) {
+                long msgId = ContentUris.parseId(uri);
+                updateAddress(msgId, addrType, array);
+                if (addrType == PduHeaders.TO) {
+                    for (EncodedStringValue v : array) {
+                        if (v != null) {
+                            recipients.add(v.getString());
+                        }
+                    }
+                }
+            }
+        }
+        if (!recipients.isEmpty()) {
+            long threadId = Threads.getOrCreateThreadId(mContext, recipients);
+            values.put(Mms.THREAD_ID, threadId);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+    }
+
+    private void updatePart(Uri uri, PduPart part, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        ContentValues values = new ContentValues(7);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+        // Only update the data when:
+        // 1. New binary data supplied or
+        // 2. The Uri of the part is different from the current one.
+        if ((part.getData() != null)
+                || (!uri.equals(part.getDataUri()))) {
+            persistData(part, uri, contentType, preOpenedFiles);
+        }
+    }
+
+    /**
+     * Update all parts of a PDU.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param body New message body of the PDU.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    @UnsupportedAppUsage
+    public void updateParts(Uri uri, PduBody body, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        try {
+            PduCacheEntry cacheEntry;
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
+                    }
+                    try {
+                        PDU_CACHE_INSTANCE.wait();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "updateParts: ", e);
+                    }
+                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+                    if (cacheEntry != null) {
+                        ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+                    }
+                }
+                // Tell the cache to indicate to other callers that this item
+                // is currently being updated.
+                PDU_CACHE_INSTANCE.setUpdating(uri, true);
+            }
+
+            ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+            HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
+
+            int partsNum = body.getPartsNum();
+            StringBuilder filter = new StringBuilder().append('(');
+            for (int i = 0; i < partsNum; i++) {
+                PduPart part = body.getPart(i);
+                Uri partUri = part.getDataUri();
+                if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority())
+                        || !partUri.getAuthority().startsWith("mms")) {
+                    toBeCreated.add(part);
+                } else {
+                    toBeUpdated.put(partUri, part);
+
+                    // Don't use 'i > 0' to determine whether we should append
+                    // 'AND' since 'i = 0' may be skipped in another branch.
+                    if (filter.length() > 1) {
+                        filter.append(" AND ");
+                    }
+
+                    filter.append(Part._ID);
+                    filter.append("!=");
+                    DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+                }
+            }
+            filter.append(')');
+
+            long msgId = ContentUris.parseId(uri);
+
+            // Remove the parts which doesn't exist anymore.
+            SqliteWrapper.delete(mContext, mContentResolver,
+                    Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+                    filter.length() > 2 ? filter.toString() : null, null);
+
+            // Create new parts which didn't exist before.
+            for (PduPart part : toBeCreated) {
+                persistPart(part, msgId, preOpenedFiles);
+            }
+
+            // Update the modified parts.
+            for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+                updatePart(e.getKey(), e.getValue(), preOpenedFiles);
+            }
+        } finally {
+            synchronized(PDU_CACHE_INSTANCE) {
+                PDU_CACHE_INSTANCE.setUpdating(uri, false);
+                PDU_CACHE_INSTANCE.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Persist a PDU object to specific location in the storage.
+     *
+     * @param pdu The PDU object to be stored.
+     * @param uri Where to store the given PDU object.
+     * @param createThreadId if true, this function may create a thread id for the recipients
+     * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used
+     *  to create the associated thread. When false, only the sender will be used in finding or
+     *  creating the appropriate thread or conversation.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @return A Uri which can be used to access the stored PDU.
+     */
+
+    @UnsupportedAppUsage
+    public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,
+            HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        if (uri == null) {
+            throw new MmsException("Uri may not be null.");
+        }
+        long msgId = -1;
+        try {
+            msgId = ContentUris.parseId(uri);
+        } catch (NumberFormatException e) {
+            // the uri ends with "inbox" or something else like that
+        }
+        boolean existingUri = msgId != -1;
+
+        if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+        synchronized(PDU_CACHE_INSTANCE) {
+            // If the cache item is getting updated, wait until it's done updating before
+            // purging it.
+            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");
+                }
+                try {
+                    PDU_CACHE_INSTANCE.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "persist1: ", e);
+                }
+            }
+        }
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        PduHeaders header = pdu.getPduHeaders();
+        PduBody body = null;
+        ContentValues values = new ContentValues();
+        Set<Entry<Integer, String>> set;
+
+        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set) {
+            int field = e.getKey();
+            EncodedStringValue encodedString = header.getEncodedStringValue(field);
+            if (encodedString != null) {
+                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
+                values.put(charsetColumn, encodedString.getCharacterSet());
+            }
+        }
+
+        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            byte[] text = header.getTextString(e.getKey());
+            if (text != null) {
+                values.put(e.getValue(), toIsoString(text));
+            }
+        }
+
+        set = OCTET_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            int b = header.getOctet(e.getKey());
+            if (b != 0) {
+                values.put(e.getValue(), b);
+            }
+        }
+
+        set = LONG_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            long l = header.getLongInteger(e.getKey());
+            if (l != -1L) {
+                values.put(e.getValue(), l);
+            }
+        }
+
+        HashMap<Integer, EncodedStringValue[]> addressMap =
+                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = header.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = header.getEncodedStringValues(addrType);
+            }
+            addressMap.put(addrType, array);
+        }
+
+        HashSet<String> recipients = new HashSet<String>();
+        int msgType = pdu.getMessageType();
+        // Here we only allocate thread ID for M-Notification.ind,
+        // M-Retrieve.conf and M-Send.req.
+        // Some of other PDU types may be allocated a thread ID outside
+        // this scope.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            switch (msgType) {
+                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                    loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
+
+                    // For received messages when group MMS is enabled, we want to associate this
+                    // message with the thread composed of all the recipients -- all but our own
+                    // number, that is. This includes the person who sent the
+                    // message or the FROM field (above) in addition to the other people the message
+                    // was addressed to or the TO field. Our own number is in that TO field and
+                    // we have to ignore it in loadRecipients.
+                    if (groupMmsEnabled) {
+                        loadRecipients(PduHeaders.TO, recipients, addressMap, true);
+
+                        // Also load any numbers in the CC field to address group messaging
+                        // compatibility issues with devices that place numbers in this field
+                        // for group messages.
+                        loadRecipients(PduHeaders.CC, recipients, addressMap, true);
+                    }
+                    break;
+                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                    loadRecipients(PduHeaders.TO, recipients, addressMap, false);
+                    break;
+            }
+            long threadId = 0;
+            if (createThreadId && !recipients.isEmpty()) {
+                // Given all the recipients associated with this message, find (or create) the
+                // correct thread.
+                threadId = Threads.getOrCreateThreadId(mContext, recipients);
+            }
+            values.put(Mms.THREAD_ID, threadId);
+        }
+
+        // Save parts first to avoid inconsistent message is loaded
+        // while saving the parts.
+        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+
+        // Figure out if this PDU is a text-only message
+        boolean textOnly = true;
+
+        // Sum up the total message size
+        int messageSize = 0;
+
+        // Get body if the PDU is a RetrieveConf or SendReq.
+        if (pdu instanceof MultimediaMessagePdu) {
+            body = ((MultimediaMessagePdu) pdu).getBody();
+            // Start saving parts if necessary.
+            if (body != null) {
+                int partsNum = body.getPartsNum();
+                if (partsNum > 2) {
+                    // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
+                    // Down a few lines below we're checking to make sure we've only got SMIL or
+                    // text. We also have to check then we don't have more than two parts.
+                    // Otherwise, a slideshow with two text slides would be marked as textOnly.
+                    textOnly = false;
+                }
+                for (int i = 0; i < partsNum; i++) {
+                    PduPart part = body.getPart(i);
+                    messageSize += part.getDataLength();
+                    persistPart(part, dummyId, preOpenedFiles);
+
+                    // If we've got anything besides text/plain or SMIL part, then we've got
+                    // an mms message with some other type of attachment.
+                    String contentType = getPartContentType(part);
+                    if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
+                            && !ContentType.TEXT_PLAIN.equals(contentType)) {
+                        textOnly = false;
+                    }
+                }
+            }
+        }
+        // Record whether this mms message is a simple plain text or not. This is a hint for the
+        // UI.
+        values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
+        // The message-size might already have been inserted when parsing the
+        // PDU header. If not, then we insert the message size as well.
+        if (values.getAsInteger(Mms.MESSAGE_SIZE) == null) {
+            values.put(Mms.MESSAGE_SIZE, messageSize);
+        }
+
+        Uri res = null;
+        if (existingUri) {
+            res = uri;
+            SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
+        } else {
+            res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+            if (res == null) {
+                throw new MmsException("persist() failed: return null.");
+            }
+            // Get the real ID of the PDU and update all parts which were
+            // saved with the dummy ID.
+            msgId = ContentUris.parseId(res);
+        }
+
+        values = new ContentValues(1);
+        values.put(Part.MSG_ID, msgId);
+        SqliteWrapper.update(mContext, mContentResolver,
+                             Uri.parse("content://mms/" + dummyId + "/part"),
+                             values, null, null);
+        // We should return the longest URI of the persisted PDU, for
+        // example, if input URI is "content://mms/inbox" and the _ID of
+        // persisted PDU is '8', we should return "content://mms/inbox/8"
+        // instead of "content://mms/8".
+        // FIXME: Should the MmsProvider be responsible for this???
+        if (!existingUri) {
+            res = Uri.parse(uri + "/" + msgId);
+        }
+
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = addressMap.get(addrType);
+            if (array != null) {
+                persistAddress(msgId, addrType, array);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * For a given address type, extract the recipients from the headers.
+     *
+     * @param addressType can be PduHeaders.FROM, PduHeaders.TO or PduHeaders.CC
+     * @param recipients a HashSet that is loaded with the recipients from the FROM, TO or CC headers
+     * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
+     * @param excludeMyNumber if true, the number of this phone will be excluded from recipients
+     */
+    @UnsupportedAppUsage
+    private void loadRecipients(int addressType, HashSet<String> recipients,
+            HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
+        EncodedStringValue[] array = addressMap.get(addressType);
+        if (array == null) {
+            return;
+        }
+        // If the TO recipients is only a single address, then we can skip loadRecipients when
+        // we're excluding our own number because we know that address is our own.
+        if (excludeMyNumber && array.length == 1) {
+            return;
+        }
+        final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+        final Set<String> myPhoneNumbers = new HashSet<String>();
+        if (excludeMyNumber) {
+            // Build a list of my phone numbers from the various sims.
+            for (int subid : subscriptionManager.getActiveSubscriptionIdList()) {
+                final String myNumber = mTelephonyManager.getLine1Number(subid);
+                if (myNumber != null) {
+                    myPhoneNumbers.add(myNumber);
+                }
+            }
+        }
+
+        for (EncodedStringValue v : array) {
+            if (v != null) {
+                final String number = v.getString();
+                if (excludeMyNumber) {
+                    for (final String myNumber : myPhoneNumbers) {
+                        if (!PhoneNumberUtils.compare(number, myNumber)
+                                && !recipients.contains(number)) {
+                            // Only add numbers which aren't my own number.
+                            recipients.add(number);
+                            break;
+                        }
+                    }
+                } else if (!recipients.contains(number)){
+                    recipients.add(number);
+                }
+            }
+        }
+    }
+
+    /**
+     * Move a PDU object from one location to another.
+     *
+     * @param from Specify the PDU object to be moved.
+     * @param to The destination location, should be one of the following:
+     *        "content://mms/inbox", "content://mms/sent",
+     *        "content://mms/drafts", "content://mms/outbox",
+     *        "content://mms/trash".
+     * @return New Uri of the moved PDU.
+     * @throws MmsException Error occurred while moving the message.
+     */
+    @UnsupportedAppUsage
+    public Uri move(Uri from, Uri to) throws MmsException {
+        // Check whether the 'msgId' has been assigned a valid value.
+        long msgId = ContentUris.parseId(from);
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Get corresponding int value of destination box.
+        Integer msgBox = MESSAGE_BOX_MAP.get(to);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+
+        ContentValues values = new ContentValues(1);
+        values.put(Mms.MESSAGE_BOX, msgBox);
+        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+        return ContentUris.withAppendedId(to, msgId);
+    }
+
+    /**
+     * Wrap a byte[] into a String.
+     */
+    @UnsupportedAppUsage
+    public static String toIsoString(byte[] bytes) {
+        try {
+            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return "";
+        }
+    }
+
+    /**
+     * Unpack a given String into a byte[].
+     */
+    @UnsupportedAppUsage
+    public static byte[] getBytes(String data) {
+        try {
+            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return new byte[0];
+        }
+    }
+
+    /**
+     * Remove all objects in the temporary path.
+     */
+    public void release() {
+        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+    }
+
+    /**
+     * Find all messages to be sent or downloaded before certain time.
+     */
+    @UnsupportedAppUsage
+    public Cursor getPendingMessages(long dueTime) {
+        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+        uriBuilder.appendQueryParameter("protocol", "mms");
+
+        String selection = PendingMessages.ERROR_TYPE + " < ?"
+                + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+        String[] selectionArgs = new String[] {
+                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+                String.valueOf(dueTime)
+        };
+
+        return SqliteWrapper.query(mContext, mContentResolver,
+                uriBuilder.build(), null, selection, selectionArgs,
+                PendingMessages.DUE_TIME);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..9d6535c
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+    private static byte ESCAPE_CHAR = '=';
+
+    /**
+     * Decodes an array quoted-printable characters into an array of original bytes.
+     * Escaped characters are converted back to their original representation.
+     *
+     * <p>
+     * This function implements a subset of
+     * quoted-printable encoding specification (rule #1 and rule #2)
+     * as defined in RFC 1521.
+     * </p>
+     *
+     * @param bytes array of quoted-printable characters
+     * @return array of original bytes,
+     *         null if quoted-printable decoding is unsuccessful.
+     */
+    @UnsupportedAppUsage
+    public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (int i = 0; i < bytes.length; i++) {
+            int b = bytes[i];
+            if (b == ESCAPE_CHAR) {
+                try {
+                    if('\r' == (char)bytes[i + 1] &&
+                            '\n' == (char)bytes[i + 2]) {
+                        i += 2;
+                        continue;
+                    }
+                    int u = Character.digit((char) bytes[++i], 16);
+                    int l = Character.digit((char) bytes[++i], 16);
+                    if (u == -1 || l == -1) {
+                        return null;
+                    }
+                    buffer.write((char) ((u << 4) + l));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    return null;
+                }
+            } else {
+                buffer.write(b);
+            }
+        }
+        return buffer.toByteArray();
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..e38c62d
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadOrigInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public ReadOrigInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    ReadOrigInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadRecInd.java b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..9696bc2
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadRecInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-ReadRec.ind pdu.
+     *
+     * @param from the from value
+     * @param messageId the message ID value
+     * @param mmsVersion current viersion of mms
+     * @param readStatus the read status value
+     * @param to the to value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if messageId or to is null.
+     */
+    @UnsupportedAppUsage
+    public ReadRecInd(EncodedStringValue from,
+                      byte[] messageId,
+                      int mmsVersion,
+                      int readStatus,
+                      EncodedStringValue[] to) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+        setFrom(from);
+        setMessageId(messageId);
+        setMmsVersion(mmsVersion);
+        setTo(to);
+        setReadStatus(readStatus);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    ReadRecInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/RetrieveConf.java b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..03755af
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Retrieve.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    @UnsupportedAppUsage
+    public RetrieveConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    RetrieveConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    @UnsupportedAppUsage
+    RetrieveConf(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getRetrieveStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Text value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getRetrieveText() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Text value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setRetrieveText(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public PreviouslySentByValue getPreviouslySentBy() {return null;}
+     *     public void setPreviouslySentBy(PreviouslySentByValue value) {}
+     *
+     *     public PreviouslySentDateValue getPreviouslySentDate() {}
+     *     public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendConf.java b/telephony/java/com/google/android/mms/pdu/SendConf.java
new file mode 100644
index 0000000..b859827
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendConf.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 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.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendConf extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    @UnsupportedAppUsage
+    public SendConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    SendConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Response-Status.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getResponseStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Response-Status.
+     *
+     * @param value the values
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setResponseStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *    public byte[] getContentLocation() {return null;}
+     *    public void setContentLocation(byte[] value) {}
+     *
+     *    public EncodedStringValue getResponseText() {return null;}
+     *    public void setResponseText(EncodedStringValue value) {}
+     *
+     *    public byte getStoreStatus() {return 0x00;}
+     *    public void setStoreStatus(byte value) {}
+     *
+     *    public byte[] getStoreStatusText() {return null;}
+     *    public void setStoreStatusText(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendReq.java b/telephony/java/com/google/android/mms/pdu/SendReq.java
new file mode 100644
index 0000000..c1b7f93
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendReq.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendReq extends MultimediaMessagePdu {
+    private static final String TAG = "SendReq";
+
+    @UnsupportedAppUsage
+    public SendReq() {
+        super();
+
+        try {
+            setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+            setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+            // FIXME: Content-type must be decided according to whether
+            // SMIL part present.
+            setContentType("application/vnd.wap.multipart.related".getBytes());
+            setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+            setTransactionId(generateTransactionId());
+        } catch (InvalidHeaderValueException e) {
+            // Impossible to reach here since all headers we set above are valid.
+            Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private byte[] generateTransactionId() {
+        String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+        return transactionId.getBytes();
+    }
+
+    /**
+     * Constructor, used when composing a M-Send.req pdu.
+     *
+     * @param contentType the content type value
+     * @param from the from value
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if contentType, form or transactionId is null.
+     */
+    public SendReq(byte[] contentType,
+                   EncodedStringValue from,
+                   int mmsVersion,
+                   byte[] transactionId) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        setContentType(contentType);
+        setFrom(from);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendReq(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    @UnsupportedAppUsage
+    SendReq(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get Bcc value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getBcc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+    }
+
+    /**
+     * Add a "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addBcc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Set "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setBcc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Set "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setCc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get X-Mms-MessageSize value.
+     *
+     * Expiry-value = size of message
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-MessageSize value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte getAdaptationAllowed() {return 0};
+     *     public void setAdaptationAllowed(btye value) {};
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public long getDeliveryTime() {return 0};
+     *     public void setDeliveryTime(long value) {};
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getStore() {return 0x00;}
+     *     public void setStore(byte value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/package.html b/telephony/java/com/google/android/mms/pdu/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/util/AbstractCache.java b/telephony/java/com/google/android/mms/util/AbstractCache.java
new file mode 100644
index 0000000..ab5d48a
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/AbstractCache.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+
+public abstract class AbstractCache<K, V> {
+    private static final String TAG = "AbstractCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final int MAX_CACHED_ITEMS  = 500;
+
+    private final HashMap<K, CacheEntry<V>> mCacheMap;
+
+    @UnsupportedAppUsage
+    protected AbstractCache() {
+        mCacheMap = new HashMap<K, CacheEntry<V>>();
+    }
+
+    @UnsupportedAppUsage
+    public boolean put(K key, V value) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to put " + key + " into cache.");
+        }
+
+        if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+            // TODO Should remove the oldest or least hit cached entry
+            // and then cache the new one.
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Failed! size limitation reached.");
+            }
+            return false;
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = new CacheEntry<V>();
+            cacheEntry.value = value;
+            mCacheMap.put(key, cacheEntry);
+
+            if (LOCAL_LOGV) {
+                Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage
+    public V get(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to get " + key + " from cache.");
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = mCacheMap.get(key);
+            if (cacheEntry != null) {
+                cacheEntry.hit++;
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+                }
+                return cacheEntry.value;
+            }
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    public V purge(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to purge " + key);
+        }
+
+        CacheEntry<V> v = mCacheMap.remove(key);
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, mCacheMap.size() + " items cached.");
+        }
+
+        return v != null ? v.value : null;
+    }
+
+    @UnsupportedAppUsage
+    public void purgeAll() {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purging cache, " + mCacheMap.size()
+                    + " items dropped.");
+        }
+        mCacheMap.clear();
+    }
+
+    public int size() {
+        return mCacheMap.size();
+    }
+
+    private static class CacheEntry<V> {
+        int hit;
+        V value;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
new file mode 100644
index 0000000..118de46
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class DownloadDrmHelper {
+    private static final String TAG = "DownloadDrmHelper";
+
+    /** The MIME type of special DRM files */
+    public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+    /** The extensions of special DRM files */
+    public static final String EXTENSION_DRM_MESSAGE = ".dm";
+
+    public static final String EXTENSION_INTERNAL_FWDL = ".fl";
+
+    /**
+     * Checks if the Media Type is a DRM Media Type
+     *
+     * @param drmManagerClient A DrmManagerClient
+     * @param mimetype Media Type to check
+     * @return True if the Media Type is DRM else false
+     */
+    public static boolean isDrmMimeType(Context context, String mimetype) {
+        boolean result = false;
+        if (context != null) {
+            try {
+                DrmManagerClient drmClient = new DrmManagerClient(context);
+                if (drmClient != null && mimetype != null && mimetype.length() > 0) {
+                    result = drmClient.canHandle("", mimetype);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks if the Media Type needs to be DRM converted
+     *
+     * @param mimetype Media type of the content
+     * @return True if convert is needed else false
+     */
+    @UnsupportedAppUsage
+    public static boolean isDrmConvertNeeded(String mimetype) {
+        return MIMETYPE_DRM_MESSAGE.equals(mimetype);
+    }
+
+    /**
+     * Modifies the file extension for a DRM Forward Lock file NOTE: This
+     * function shouldn't be called if the file shouldn't be DRM converted
+     */
+    @UnsupportedAppUsage
+    public static String modifyDrmFwLockFileExtension(String filename) {
+        if (filename != null) {
+            int extensionIndex;
+            extensionIndex = filename.lastIndexOf(".");
+            if (extensionIndex != -1) {
+                filename = filename.substring(0, extensionIndex);
+            }
+            filename = filename.concat(EXTENSION_INTERNAL_FWDL);
+        }
+        return filename;
+    }
+
+    /**
+     * Gets the original mime type of DRM protected content.
+     *
+     * @param context The context
+     * @param path Path to the file
+     * @param containingMime The current mime type of the file i.e. the
+     *            containing mime type
+     * @return The original mime type of the file if DRM protected else the
+     *         currentMime
+     */
+    public static String getOriginalMimeType(Context context, String path, String containingMime) {
+        String result = containingMime;
+        DrmManagerClient drmClient = new DrmManagerClient(context);
+        try {
+            if (drmClient.canHandle(path, null)) {
+                result = drmClient.getOriginalMimeType(path);
+            }
+        } catch (IllegalArgumentException ex) {
+            Log.w(TAG,
+                    "Can't get original mime type since path is null or empty string.");
+        } catch (IllegalStateException ex) {
+            Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+        }
+        return result;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/DrmConvertSession.java b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
new file mode 100644
index 0000000..0e8ec91
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmConvertedStatus;
+import android.drm.DrmManagerClient;
+import android.provider.Downloads;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DrmConvertSession {
+    private DrmManagerClient mDrmClient;
+    private int mConvertSessionId;
+    private static final String TAG = "DrmConvertSession";
+
+    private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
+        mDrmClient = drmClient;
+        mConvertSessionId = convertSessionId;
+    }
+
+    /**
+     * Start of converting a file.
+     *
+     * @param context The context of the application running the convert session.
+     * @param mimeType Mimetype of content that shall be converted.
+     * @return A convert session or null in case an error occurs.
+     */
+    @UnsupportedAppUsage
+    public static DrmConvertSession open(Context context, String mimeType) {
+        DrmManagerClient drmClient = null;
+        int convertSessionId = -1;
+        if (context != null && mimeType != null && !mimeType.equals("")) {
+            try {
+                drmClient = new DrmManagerClient(context);
+                try {
+                    convertSessionId = drmClient.openConvertSession(mimeType);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "Conversion of Mimetype: " + mimeType
+                            + " is not supported.", e);
+                } catch (IllegalStateException e) {
+                    Log.w(TAG, "Could not access Open DrmFramework.", e);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+
+        if (drmClient == null || convertSessionId < 0) {
+            return null;
+        } else {
+            return new DrmConvertSession(drmClient, convertSessionId);
+        }
+    }
+    /**
+     * Convert a buffer of data to protected format.
+     *
+     * @param buffer Buffer filled with data to convert.
+     * @param size The number of bytes that shall be converted.
+     * @return A Buffer filled with converted data, if execution is ok, in all
+     *         other case null.
+     */
+    @UnsupportedAppUsage
+    public byte [] convert(byte[] inBuffer, int size) {
+        byte[] result = null;
+        if (inBuffer != null) {
+            DrmConvertedStatus convertedStatus = null;
+            try {
+                if (size != inBuffer.length) {
+                    byte[] buf = new byte[size];
+                    System.arraycopy(inBuffer, 0, buf, 0, size);
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
+                } else {
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
+                }
+
+                if (convertedStatus != null &&
+                        convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
+                        convertedStatus.convertedData != null) {
+                    result = convertedStatus.convertedData;
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
+                        + mConvertSessionId, e);
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not convert data. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        } else {
+            throw new IllegalArgumentException("Parameter inBuffer is null");
+        }
+        return result;
+    }
+
+    /**
+     * Ends a conversion session of a file.
+     *
+     * @param fileName The filename of the converted file.
+     * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
+     *         Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
+     *         be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
+     *         occurs when accessing drm framework.
+     *         Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
+     */
+    @UnsupportedAppUsage
+    public int close(String filename) {
+        DrmConvertedStatus convertedStatus = null;
+        int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
+        if (mDrmClient != null && mConvertSessionId >= 0) {
+            try {
+                convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
+                if (convertedStatus == null ||
+                        convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
+                        convertedStatus.convertedData == null) {
+                    result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
+                } else {
+                    RandomAccessFile rndAccessFile = null;
+                    try {
+                        rndAccessFile = new RandomAccessFile(filename, "rw");
+                        rndAccessFile.seek(convertedStatus.offset);
+                        rndAccessFile.write(convertedStatus.convertedData);
+                        result = Downloads.Impl.STATUS_SUCCESS;
+                    } catch (FileNotFoundException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "File: " + filename + " could not be found.", e);
+                    } catch (IOException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not access File: " + filename + " .", e);
+                    } catch (IllegalArgumentException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not open file in mode: rw", e);
+                    } catch (SecurityException e) {
+                        Log.w(TAG, "Access to File: " + filename +
+                                " was denied denied by SecurityManager.", e);
+                    } finally {
+                        if (rndAccessFile != null) {
+                            try {
+                                rndAccessFile.close();
+                            } catch (IOException e) {
+                                result = Downloads.Impl.STATUS_FILE_ERROR;
+                                Log.w(TAG, "Failed to close File:" + filename
+                                        + ".", e);
+                            }
+                        }
+                    }
+                }
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not close convertsession. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        }
+        return result;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCache.java b/telephony/java/com/google/android/mms/util/PduCache.java
new file mode 100644
index 0000000..94e3894
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCache.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+    private static final String TAG = "PduCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final int MMS_ALL             = 0;
+    private static final int MMS_ALL_ID          = 1;
+    private static final int MMS_INBOX           = 2;
+    private static final int MMS_INBOX_ID        = 3;
+    private static final int MMS_SENT            = 4;
+    private static final int MMS_SENT_ID         = 5;
+    private static final int MMS_DRAFTS          = 6;
+    private static final int MMS_DRAFTS_ID       = 7;
+    private static final int MMS_OUTBOX          = 8;
+    private static final int MMS_OUTBOX_ID       = 9;
+    private static final int MMS_CONVERSATION    = 10;
+    private static final int MMS_CONVERSATION_ID = 11;
+
+    private static final UriMatcher URI_MATCHER;
+    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+    private static PduCache sInstance;
+
+    static {
+        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URI_MATCHER.addURI("mms", null,         MMS_ALL);
+        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
+        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
+        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
+        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
+        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
+        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
+        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
+        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
+        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
+        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
+        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+    }
+
+    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
+    private final HashMap<Long, HashSet<Uri>> mThreads;
+    private final HashSet<Uri> mUpdating;
+
+    @UnsupportedAppUsage
+    private PduCache() {
+        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
+        mThreads = new HashMap<Long, HashSet<Uri>>();
+        mUpdating = new HashSet<Uri>();
+    }
+
+    @UnsupportedAppUsage
+    synchronized public static final PduCache getInstance() {
+        if (sInstance == null) {
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Constructing new PduCache instance.");
+            }
+            sInstance = new PduCache();
+        }
+        return sInstance;
+    }
+
+    @Override
+    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
+        int msgBoxId = entry.getMessageBox();
+        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+        if (msgBox == null) {
+            msgBox = new HashSet<Uri>();
+            mMessageBoxes.put(msgBoxId, msgBox);
+        }
+
+        long threadId = entry.getThreadId();
+        HashSet<Uri> thread = mThreads.get(threadId);
+        if (thread == null) {
+            thread = new HashSet<Uri>();
+            mThreads.put(threadId, thread);
+        }
+
+        Uri finalKey = normalizeKey(uri);
+        boolean result = super.put(finalKey, entry);
+        if (result) {
+            msgBox.add(finalKey);
+            thread.add(finalKey);
+        }
+        setUpdating(uri, false);
+        return result;
+    }
+
+    synchronized public void setUpdating(Uri uri, boolean updating) {
+        if (updating) {
+            mUpdating.add(uri);
+        } else {
+            mUpdating.remove(uri);
+        }
+    }
+
+    @UnsupportedAppUsage
+    synchronized public boolean isUpdating(Uri uri) {
+        return mUpdating.contains(uri);
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    synchronized public PduCacheEntry purge(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case MMS_ALL_ID:
+                return purgeSingleEntry(uri);
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+            // Implicit batch of purge, return null.
+            case MMS_ALL:
+            case MMS_CONVERSATION:
+                purgeAll();
+                return null;
+            case MMS_INBOX:
+            case MMS_SENT:
+            case MMS_DRAFTS:
+            case MMS_OUTBOX:
+                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+                return null;
+            case MMS_CONVERSATION_ID:
+                purgeByThreadId(ContentUris.parseId(uri));
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    private PduCacheEntry purgeSingleEntry(Uri key) {
+        mUpdating.remove(key);
+        PduCacheEntry entry = super.purge(key);
+        if (entry != null) {
+            removeFromThreads(key, entry);
+            removeFromMessageBoxes(key, entry);
+            return entry;
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    @Override
+    synchronized public void purgeAll() {
+        super.purgeAll();
+
+        mMessageBoxes.clear();
+        mThreads.clear();
+        mUpdating.clear();
+    }
+
+    /**
+     * @param uri The Uri to be normalized.
+     * @return Uri The normalized key of cached entry.
+     */
+    private Uri normalizeKey(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        Uri normalizedKey = null;
+
+        switch (match) {
+            case MMS_ALL_ID:
+                normalizedKey = uri;
+                break;
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+                break;
+            default:
+                return null;
+        }
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, uri + " -> " + normalizedKey);
+        }
+        return normalizedKey;
+    }
+
+    private void purgeByMessageBox(Integer msgBoxId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+        }
+
+        if (msgBoxId != null) {
+            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
+            if (msgBox != null) {
+                for (Uri key : msgBox) {
+                    mUpdating.remove(key);
+                    PduCacheEntry entry = super.purge(key);
+                    if (entry != null) {
+                        removeFromThreads(key, entry);
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeFromThreads(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+        if (thread != null) {
+            thread.remove(key);
+        }
+    }
+
+    private void purgeByThreadId(long threadId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in thread: " + threadId);
+        }
+
+        HashSet<Uri> thread = mThreads.remove(threadId);
+        if (thread != null) {
+            for (Uri key : thread) {
+                mUpdating.remove(key);
+                PduCacheEntry entry = super.purge(key);
+                if (entry != null) {
+                    removeFromMessageBoxes(key, entry);
+                }
+            }
+        }
+    }
+
+    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
+        if (msgBox != null) {
+            msgBox.remove(key);
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCacheEntry.java b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
new file mode 100644
index 0000000..1ecd1bf
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+    private final GenericPdu mPdu;
+    private final int mMessageBox;
+    private final long mThreadId;
+
+    @UnsupportedAppUsage
+    public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+        mPdu = pdu;
+        mMessageBox = msgBox;
+        mThreadId = threadId;
+    }
+
+    @UnsupportedAppUsage
+    public GenericPdu getPdu() {
+        return mPdu;
+    }
+
+    @UnsupportedAppUsage
+    public int getMessageBox() {
+        return mMessageBox;
+    }
+
+    @UnsupportedAppUsage
+    public long getThreadId() {
+        return mThreadId;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/SqliteWrapper.java b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
new file mode 100644
index 0000000..2dd1dc1
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public final class SqliteWrapper {
+    private static final String TAG = "SqliteWrapper";
+    private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
+                = "unable to open database file";
+
+    private SqliteWrapper() {
+        // Forbidden being instantiated.
+    }
+
+    // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
+    // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
+    private static boolean isLowMemory(Context context) {
+        if (null == context) {
+            return false;
+        }
+
+        ActivityManager am = (ActivityManager)
+                        context.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
+        am.getMemoryInfo(outInfo);
+
+        return outInfo.lowMemory;
+    }
+
+    // FIXME: need to optimize this method.
+    private static boolean isLowMemory(SQLiteException e) {
+        return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
+    }
+
+    @UnsupportedAppUsage
+    public static void checkSQLiteException(Context context, SQLiteException e) {
+        if (isLowMemory(e)) {
+            Toast.makeText(context, com.android.internal.R.string.low_memory,
+                    Toast.LENGTH_SHORT).show();
+        } else {
+            throw e;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+            String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        try {
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when query: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static boolean requery(Context context, Cursor cursor) {
+        try {
+            return cursor.requery();
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when requery: ", e);
+            checkSQLiteException(context, e);
+            return false;
+        }
+    }
+    @UnsupportedAppUsage
+    public static int update(Context context, ContentResolver resolver, Uri uri,
+            ContentValues values, String where, String[] selectionArgs) {
+        try {
+            return resolver.update(uri, values, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when update: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static int delete(Context context, ContentResolver resolver, Uri uri,
+            String where, String[] selectionArgs) {
+        try {
+            return resolver.delete(uri, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when delete: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static Uri insert(Context context, ContentResolver resolver,
+            Uri uri, ContentValues values) {
+        try {
+            return resolver.insert(uri, values);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when insert: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/package.html b/telephony/java/com/google/android/mms/util/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 9a653cf..81b1e49 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -20,13 +20,12 @@
     name: "android.test.mock",
 
     srcs: ["src/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
 
     api_packages: [
         "android.test.mock",
     ],
-
-    srcs_lib: "framework-minus-apex",
-    srcs_lib_whitelist_pkgs: ["android"],
     compile_dex: true,
 }
 
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
index aec9f77..7291dc7 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
+++ b/tests/BackgroundDexOptServiceIntegrationTests/AndroidManifest.xml
@@ -28,6 +28,8 @@
     <uses-permission android:name="android.permission.SET_TIME" />
     <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
index 7d826f7..4cd56c3 100644
--- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
+++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java
@@ -22,6 +22,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemProperties;
 import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -30,7 +31,9 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
@@ -52,6 +55,13 @@
  * 3. Under low storage conditions and package is recently used, check
  * that dexopt upgrades test app to $(getprop pm.dexopt.bg-dexopt).
  *
+ * When downgrade feature is on (downgrade_unused_apps_enabled flag is set to true):
+ * 4  On low storage, check that the inactive packages are downgraded.
+ * 5. On low storage, check that used packages are upgraded.
+ * 6. On storage completely full, dexopt fails.
+ * 7. Not on low storage, unused packages are upgraded.
+ * 8. Low storage, unused app is downgraded. When app is used again, app is upgraded.
+ *
  * Each test case runs "cmd package bg-dexopt-job com.android.frameworks.bgdexopttest".
  *
  * The setup for these tests make sure this package has been configured to have been recently used
@@ -59,6 +69,10 @@
  * recently used, it sets the time forward more than
  * `getprop pm.dexopt.downgrade_after_inactive_days` days.
  *
+ * For some of the tests, the DeviceConfig flags inactive_app_threshold_days and
+ * downgrade_unused_apps_enabled are set. These turn on/off the downgrade unused apps feature for
+ * all devices and set the time threshold for unused apps.
+ *
  * For tests that require low storage, the phone is filled up.
  *
  * Run with "atest BackgroundDexOptServiceIntegrationTests".
@@ -80,10 +94,14 @@
             "pm.dexopt.downgrade_after_inactive_days", 0);
     // Needs to be between 1.0 and 2.0.
     private static final double LOW_STORAGE_MULTIPLIER = 1.5;
+    private static final int DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS = 15;
 
     // The file used to fill up storage.
     private File mBigFile;
 
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
     // Remember start time.
     @BeforeClass
     public static void setUpAll() {
@@ -196,11 +214,27 @@
         logSpaceRemaining();
     }
 
+    private void fillUpStorageCompletely() throws IOException {
+        fillUpStorage((getStorageLowBytes()));
+    }
+
     // Fill up storage so that device is in low storage condition.
     private void fillUpToLowStorage() throws IOException {
         fillUpStorage((long) (getStorageLowBytes() * LOW_STORAGE_MULTIPLIER));
     }
 
+    private void setInactivePackageThreshold(int threshold) {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                "inactive_app_threshold_days", Integer.toString(threshold), false);
+    }
+
+    private void enableDowngradeFeature(boolean enabled) {
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                "downgrade_unused_apps_enabled", Boolean.toString(enabled), false);
+    }
+
     // TODO(aeubanks): figure out how to get scheduled bg-dexopt to run
     private static void runBackgroundDexOpt() throws IOException {
         String result = runShellCommand("cmd package bg-dexopt-job " + PACKAGE_NAME);
@@ -244,7 +278,7 @@
 
     // Test that background dexopt under normal conditions succeeds.
     @Test
-    public void testBackgroundDexOpt() throws IOException {
+    public void testBackgroundDexOpt_normalConditions_dexOptSucceeds() throws IOException {
         // Set filter to quicken.
         compilePackageWithFilter(PACKAGE_NAME, "verify");
         Assert.assertEquals("verify", getCompilerFilter(PACKAGE_NAME));
@@ -257,17 +291,16 @@
 
     // Test that background dexopt under low storage conditions upgrades used packages.
     @Test
-    public void testBackgroundDexOptDowngradeSkipRecentlyUsedPackage() throws IOException {
+    public void testBackgroundDexOpt_lowStorage_usedPkgsUpgraded() throws IOException {
         // Should be less than DOWNGRADE_AFTER_DAYS.
         long deltaDays = DOWNGRADE_AFTER_DAYS - 1;
         try {
+            enableDowngradeFeature(false);
             // Set time to future.
             setTimeFutureDays(deltaDays);
-
             // Set filter to quicken.
             compilePackageWithFilter(PACKAGE_NAME, "quicken");
             Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
-
             // Fill up storage to trigger low storage threshold.
             fillUpToLowStorage();
 
@@ -282,18 +315,20 @@
     }
 
     // Test that background dexopt under low storage conditions downgrades unused packages.
+    // This happens if the system property pm.dexopt.downgrade_after_inactive_days is set
+    // (e.g. on Android Go devices).
     @Test
-    public void testBackgroundDexOptDowngradeSuccessful() throws IOException {
+    public void testBackgroundDexOpt_lowStorage_unusedPkgsDowngraded()
+            throws IOException {
         // Should be more than DOWNGRADE_AFTER_DAYS.
         long deltaDays = DOWNGRADE_AFTER_DAYS + 1;
         try {
+            enableDowngradeFeature(false);
             // Set time to future.
             setTimeFutureDays(deltaDays);
-
             // Set filter to quicken.
             compilePackageWithFilter(PACKAGE_NAME, "quicken");
             Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
-
             // Fill up storage to trigger low storage threshold.
             fillUpToLowStorage();
 
@@ -307,4 +342,134 @@
         }
     }
 
+    // Test that the background dexopt downgrades inactive packages when the downgrade feature is
+    // enabled.
+    @Test
+    public void testBackgroundDexOpt_downgradeFeatureEnabled_lowStorage_inactivePkgsDowngraded()
+            throws IOException {
+        // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+        long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+        try {
+            enableDowngradeFeature(true);
+            setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+            // Set time to future.
+            setTimeFutureDays(deltaDays);
+            // Set filter to quicken.
+            compilePackageWithFilter(PACKAGE_NAME, "quicken");
+            Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+            // Fill up storage to trigger low storage threshold.
+            fillUpToLowStorage();
+
+            runBackgroundDexOpt();
+
+            // Verify that downgrade is successful.
+            Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+        } finally {
+            // Reset time.
+            setTimeFutureDays(-deltaDays);
+        }
+    }
+
+    // Test that the background dexopt upgrades used packages when the downgrade feature is enabled.
+    // This test doesn't fill the device storage completely, but to a multiplier of the low storage
+    // threshold and this is why apps can still be optimized.
+    @Test
+    public void testBackgroundDexOpt_downgradeFeatureEnabled_lowStorage_usedPkgsUpgraded()
+            throws IOException {
+        enableDowngradeFeature(true);
+        // Set filter to quicken.
+        compilePackageWithFilter(PACKAGE_NAME, "quicken");
+        Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+        // Fill up storage to trigger low storage threshold.
+        fillUpToLowStorage();
+
+        runBackgroundDexOpt();
+
+        /// Verify that bg-dexopt is successful in upgrading the used packages.
+        Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+    }
+
+    // Test that the background dexopt fails and doesn't change the compilation filter of used
+    // packages when the downgrade feature is enabled and the storage is filled up completely.
+    // The bg-dexopt shouldn't optimise nor downgrade these packages.
+    @Test
+    public void testBackgroundDexOpt_downgradeFeatureEnabled_fillUpStorageCompletely_dexOptFails()
+            throws IOException {
+        enableDowngradeFeature(true);
+        String previousCompilerFilter = getCompilerFilter(PACKAGE_NAME);
+
+        // Fill up storage completely, without using a multiplier for the low storage threshold.
+        fillUpStorageCompletely();
+
+        // When the bg dexopt runs with the storage filled up completely, it will fail.
+        mExpectedException.expect(IllegalStateException.class);
+        runBackgroundDexOpt();
+
+        /// Verify that bg-dexopt doesn't change the compilation filter of used apps.
+        Assert.assertEquals(previousCompilerFilter, getCompilerFilter(PACKAGE_NAME));
+    }
+
+    // Test that the background dexopt upgrades the unused packages when the downgrade feature is
+    // on if the device is not low on storage.
+    @Test
+    public void testBackgroundDexOpt_downgradeFeatureEnabled_notLowStorage_unusedPkgsUpgraded()
+            throws IOException {
+        // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+        long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+        try {
+            enableDowngradeFeature(true);
+            setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+            // Set time to future.
+            setTimeFutureDays(deltaDays);
+            // Set filter to quicken.
+            compilePackageWithFilter(PACKAGE_NAME, "quicken");
+            Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+            runBackgroundDexOpt();
+
+            // Verify that bg-dexopt is successful in upgrading the unused packages when the device
+            // is not low on storage.
+            Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+        } finally {
+            // Reset time.
+            setTimeFutureDays(-deltaDays);
+        }
+    }
+
+    // Test that when an unused package (which was downgraded) is used again, it's re-optimized when
+    // bg-dexopt runs again.
+    @Test
+    public void testBackgroundDexOpt_downgradeFeatureEnabled_downgradedPkgsUpgradedAfterUse()
+            throws IOException {
+        // Should be more than DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS.
+        long deltaDays = DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS + 1;
+        try {
+            enableDowngradeFeature(true);
+            setInactivePackageThreshold(DOWNGRADE_FEATURE_PKG_INACTIVE_AFTER_DAYS);
+            // Set time to future.
+            setTimeFutureDays(deltaDays);
+            // Fill up storage to trigger low storage threshold.
+            fillUpToLowStorage();
+            // Set filter to quicken.
+            compilePackageWithFilter(PACKAGE_NAME, "quicken");
+            Assert.assertEquals("quicken", getCompilerFilter(PACKAGE_NAME));
+
+            runBackgroundDexOpt();
+
+            // Verify that downgrade is successful.
+            Assert.assertEquals(DOWNGRADE_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+
+            // Reset time.
+            setTimeFutureDays(-deltaDays);
+            deltaDays = 0;
+            runBackgroundDexOpt();
+
+            // Verify that bg-dexopt is successful in upgrading the unused packages that were used
+            // again.
+            Assert.assertEquals(BG_DEXOPT_COMPILER_FILTER, getCompilerFilter(PACKAGE_NAME));
+        } finally {
+            // Reset time.
+            setTimeFutureDays(-deltaDays);
+        }
+    }
 }
diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp
new file mode 100644
index 0000000..1b097a8
--- /dev/null
+++ b/tests/BootImageProfileTest/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+java_test_host {
+    name: "BootImageProfileTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed"],
+    test_suites: ["general-tests"],
+}
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
new file mode 100644
index 0000000..c132007
--- /dev/null
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+<configuration description="Config for BootImageProfileTest">
+     <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
+         furthermore the changes in /data/local.prop don't actually seem to get picked up.
+    -->
+    <target_preparer
+        class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
+        <option name="force-skip-system-props" value="true" />
+
+        <option name="run-command" value="setprop dalvik.vm.profilesystemserver true" />
+        <option name="run-command" value="setprop dalvik.vm.profilebootclasspath true" />
+
+        <!-- Profiling does not pick up the above changes we restart the shell -->
+        <option name="run-command" value="stop" />
+        <option name="run-command" value="start" />
+
+        <!-- give it some time to restart the shell; otherwise the first unit test might fail -->
+        <option name="run-command" value="sleep 2" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.bootimageprofile.BootImageProfileTest" />
+    </test>
+</configuration>
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
new file mode 100644
index 0000000..1b569f9
--- /dev/null
+++ b/tests/BootImageProfileTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "BootImageProfileTest"
+    }
+  ]
+}
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
new file mode 100644
index 0000000..17986a3
--- /dev/null
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.bootimageprofile;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class BootImageProfileTest implements IDeviceTest {
+    private ITestDevice mTestDevice;
+    private static final String SYSTEM_SERVER_PROFILE =
+            "/data/misc/profiles/cur/0/android/primary.prof";
+
+    @Override
+    public void setDevice(ITestDevice testDevice) {
+        mTestDevice = testDevice;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mTestDevice;
+    }
+
+    /**
+     * Test that the boot image profile properties are set.
+     */
+    @Test
+    public void testProperties() throws Exception {
+        String res = mTestDevice.getProperty("dalvik.vm.profilebootclasspath");
+        assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
+        res = mTestDevice.getProperty("dalvik.vm.profilesystemserver");
+        assertTrue("profile system server not enabled", res != null && res.equals("true"));
+    }
+
+    private void forceSaveProfile(String pkg) throws Exception {
+        String pid = mTestDevice.executeShellCommand("pidof " + pkg).trim();
+        assertTrue("Invalid pid " + pid, pid.length() > 0);
+        String res = mTestDevice.executeShellCommand("kill -s SIGUSR1 " + pid).trim();
+        assertTrue("kill SIGUSR1: " + res, res.length() == 0);
+    }
+
+    @Test
+    public void testSystemServerProfile() throws Exception {
+        // Trunacte the profile before force it to be saved to prevent previous profiles
+        // causing the test to pass.
+        String res;
+        res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim();
+        assertTrue(res, res.length() == 0);
+        // Force save profiles in case the system just started.
+        Thread.sleep(1000);
+        forceSaveProfile("system_server");
+        Thread.sleep(2000);
+        // Validate that the profile is non empty.
+        res = mTestDevice.executeShellCommand("profman --dump-only --profile-file="
+                + SYSTEM_SERVER_PROFILE);
+        boolean sawFramework = false;
+        boolean sawServices = false;
+        for (String line : res.split("\n")) {
+            if (line.contains("framework.jar")) {
+                sawFramework = true;
+            } else if (line.contains("services.jar")) {
+                sawServices = true;
+            }
+        }
+        assertTrue("Did not see framework.jar in " + res, sawFramework);
+        assertTrue("Did not see services.jar in " + res, sawServices);
+    }
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 30bb3ef..f0c5baa 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,7 +342,7 @@
 
 
 
-    // Code below generated by codegen v1.0.0.
+    // Code below generated by codegen v1.0.1.
     //
     // DO NOT MODIFY!
     //
@@ -1798,8 +1798,8 @@
     }
 
     @DataClass.Generated(
-            time = 1565126122525L,
-            codegenVersion = "1.0.0",
+            time = 1568235365376L,
+            codegenVersion = "1.0.1",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
             inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_UNDEFINED\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static  java.lang.String defaultName4()\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index 11f03a7..86f37fe 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -74,7 +74,7 @@
 
 
 
-    // Code below generated by codegen v1.0.0.
+    // Code below generated by codegen v1.0.1.
     //
     // DO NOT MODIFY!
     //
@@ -176,8 +176,8 @@
     }
 
     @DataClass.Generated(
-            time = 1565126123496L,
-            codegenVersion = "1.0.0",
+            time = 1568235366386L,
+            codegenVersion = "1.0.1",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
             inputSignatures = "  long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n  long creationTimestamp\nclass SampleWithCustomBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)\nabstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic  com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
diff --git a/tests/FlickerTests/lib/Android.bp b/tests/FlickerTests/lib/Android.bp
index 5d8ed2c..e0f0188 100644
--- a/tests/FlickerTests/lib/Android.bp
+++ b/tests/FlickerTests/lib/Android.bp
@@ -30,10 +30,23 @@
 }
 
 java_library {
+    name: "flickerlib_without_helpers",
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    exclude_srcs: ["src/**/helpers/*.java"],
+    static_libs: [
+        "cts-wm-util",
+        "platformprotosnano",
+        "layersprotosnano",
+        "truth-prebuilt"
+    ],
+}
+
+java_library {
     name: "flickerautomationhelperlib",
     sdk_version: "test_current",
     srcs: [
-        "src/com/android/server/wm/flicker/AutomationUtils.java",
+        "src/com/android/server/wm/flicker/helpers/AutomationUtils.java",
         "src/com/android/server/wm/flicker/WindowUtils.java",
     ],
     static_libs: [
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
index 84f9f871..38255ee 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
@@ -24,14 +24,14 @@
  * results. Assertions are functions that are applied over a single trace entry and returns a
  * result which includes a detailed reason if the assertion fails.
  */
-class Assertions {
+public class Assertions {
     /**
      * Checks assertion on a single trace entry.
      *
      * @param <T> trace entry type to perform the assertion on.
      */
     @FunctionalInterface
-    interface TraceAssertion<T> extends Function<T, Result> {
+    public interface TraceAssertion<T> extends Function<T, Result> {
         /**
          * Returns an assertion that represents the logical negation of this assertion.
          *
@@ -46,7 +46,7 @@
      * Checks assertion on a single layers trace entry.
      */
     @FunctionalInterface
-    interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
+    public interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
 
     }
 
@@ -54,11 +54,11 @@
      * Utility class to store assertions with an identifier to help generate more useful debug
      * data when dealing with multiple assertions.
      */
-    static class NamedAssertion<T> {
-        final TraceAssertion<T> assertion;
-        final String name;
+    public static class NamedAssertion<T> {
+        public final TraceAssertion<T> assertion;
+        public final String name;
 
-        NamedAssertion(TraceAssertion<T> assertion, String name) {
+        public NamedAssertion(TraceAssertion<T> assertion, String name) {
             this.assertion = assertion;
             this.name = name;
         }
@@ -67,21 +67,21 @@
     /**
      * Contains the result of an assertion including the reason for failed assertions.
      */
-    static class Result {
-        static final String NEGATION_PREFIX = "!";
-        final boolean success;
-        final long timestamp;
-        final String assertionName;
-        final String reason;
+    public static class Result {
+        public static final String NEGATION_PREFIX = "!";
+        public final boolean success;
+        public final long timestamp;
+        public final String assertionName;
+        public final String reason;
 
-        Result(boolean success, long timestamp, String assertionName, String reason) {
+        public Result(boolean success, long timestamp, String assertionName, String reason) {
             this.success = success;
             this.timestamp = timestamp;
             this.assertionName = assertionName;
             this.reason = reason;
         }
 
-        Result(boolean success, String reason) {
+        public Result(boolean success, String reason) {
             this.success = success;
             this.reason = reason;
             this.assertionName = "";
@@ -91,7 +91,7 @@
         /**
          * Returns the negated {@code Result} and adds a negation prefix to the assertion name.
          */
-        Result negate() {
+        public Result negate() {
             String negatedAssertionName;
             if (this.assertionName.startsWith(NEGATION_PREFIX)) {
                 negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1);
@@ -101,11 +101,11 @@
             return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason);
         }
 
-        boolean passed() {
+        public boolean passed() {
             return this.success;
         }
 
-        boolean failed() {
+        public boolean failed() {
             return !this.success;
         }
 
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
index 3c65d3c..5c4df81 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
@@ -38,11 +38,11 @@
     private AssertionOption mOption = AssertionOption.NONE;
     private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
 
-    void add(Assertions.TraceAssertion<T> assertion, String name) {
+    public void add(Assertions.TraceAssertion<T> assertion, String name) {
         mAssertions.add(new NamedAssertion<>(assertion, name));
     }
 
-    void filterByRange(long startTime, long endTime) {
+    public void filterByRange(long startTime, long endTime) {
         mFilterEntriesByRange = true;
         mFilterStartTime = startTime;
         mFilterEndTime = endTime;
@@ -75,7 +75,7 @@
      * @param entries list of entries to perform assertions on
      * @return list of failed assertion results
      */
-    List<Result> test(List<T> entries) {
+    public List<Result> test(List<T> entries) {
         List<T> filteredEntries;
         List<Result> failures;
 
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java
deleted file mode 100644
index e00a247..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static android.os.SystemClock.sleep;
-import static android.system.helpers.OverviewHelper.isRecentsInLauncher;
-import static android.view.Surface.ROTATION_0;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.support.test.launcherhelper.LauncherStrategyFactory;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-import android.util.Rational;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import androidx.test.InstrumentationRegistry;
-
-/**
- * Collection of UI Automation helper functions.
- */
-public class AutomationUtils {
-    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
-    private static final long FIND_TIMEOUT = 10000;
-    private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L;
-    private static final String TAG = "FLICKER";
-
-    public static void wakeUpAndGoToHomeScreen() {
-        UiDevice device = UiDevice.getInstance(InstrumentationRegistry
-                .getInstrumentation());
-        try {
-            device.wakeUp();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-        device.pressHome();
-    }
-
-    /**
-     * Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing
-     * the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly.
-     * This removes some delays when using the UIAutomator library required to create fast UI
-     * transitions.
-     */
-    static void setFastWait() {
-        Configurator.getInstance().setWaitForIdleTimeout(0);
-    }
-
-    /**
-     * Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior.
-     */
-    static void setDefaultWait() {
-        Configurator.getInstance().setWaitForIdleTimeout(10000);
-    }
-
-    public static boolean isQuickstepEnabled(UiDevice device) {
-        return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null;
-    }
-
-    public static void openQuickstep(UiDevice device) {
-        if (isQuickstepEnabled(device)) {
-            int height = device.getDisplayHeight();
-            UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"));
-
-            Rect navBarVisibleBounds;
-
-            // TODO(vishnun) investigate why this object cannot be found.
-            if (navBar != null) {
-                navBarVisibleBounds = navBar.getVisibleBounds();
-            } else {
-                Log.e(TAG, "Could not find nav bar, infer location");
-                navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0);
-            }
-
-            // Swipe from nav bar to 2/3rd down the screen.
-            device.swipe(
-                    navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(),
-                    navBarVisibleBounds.centerX(), height * 2 / 3,
-                    (navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step
-        } else {
-            try {
-                device.pressRecentApps();
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view");
-
-        // use a long timeout to wait until recents populated
-        if (device.wait(
-                Until.findObject(isRecentsInLauncher()
-                        ? getLauncherOverviewSelector(device) : RECENTS),
-                10000) == null) {
-            fail("Recents didn't appear");
-        }
-        device.waitForIdle();
-    }
-
-    static void clearRecents(UiDevice device) {
-        if (isQuickstepEnabled(device)) {
-            openQuickstep(device);
-
-            for (int i = 0; i < 5; i++) {
-                device.swipe(device.getDisplayWidth() / 2,
-                        device.getDisplayHeight() / 2, device.getDisplayWidth(),
-                        device.getDisplayHeight() / 2,
-                        5);
-
-                BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher",
-                        "clear_all_button");
-                UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100);
-                if (clearAllButton != null) {
-                    clearAllButton.click();
-                    return;
-                }
-            }
-        }
-    }
-
-    private static BySelector getLauncherOverviewSelector(UiDevice device) {
-        return By.res(device.getLauncherPackageName(), "overview_panel");
-    }
-
-    private static void longPressRecents(UiDevice device) {
-        BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps");
-        UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT);
-        assertNotNull("Unable to find recents button", recentsButton);
-        recentsButton.click(LONG_PRESS_TIMEOUT);
-    }
-
-    public static void launchSplitScreen(UiDevice device) {
-        String mLauncherPackage = LauncherStrategyFactory.getInstance(device)
-                .getLauncherStrategy().getSupportedLauncherPackage();
-
-        if (isQuickstepEnabled(device)) {
-            // Quickstep enabled
-            openQuickstep(device);
-
-            BySelector overviewIconSelector = By.res(mLauncherPackage, "icon")
-                    .clazz(View.class);
-            UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector),
-                    FIND_TIMEOUT);
-            assertNotNull("Unable to find app icon in Overview", overviewIcon);
-            overviewIcon.click();
-
-            BySelector splitscreenButtonSelector = By.text("Split screen");
-            UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector),
-                    FIND_TIMEOUT);
-            assertNotNull("Unable to find Split screen button in Overview", splitscreenButton);
-            splitscreenButton.click();
-        } else {
-            // Classic long press recents
-            longPressRecents(device);
-        }
-        // Wait for animation to complete.
-        sleep(2000);
-    }
-
-    public static void exitSplitScreen(UiDevice device) {
-        if (isQuickstepEnabled(device)) {
-            // Quickstep enabled
-            BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
-            UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
-            assertNotNull("Unable to find Split screen divider", divider);
-
-            // Drag the split screen divider to the top of the screen
-            divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400);
-        } else {
-            // Classic long press recents
-            longPressRecents(device);
-        }
-        // Wait for animation to complete.
-        sleep(2000);
-    }
-
-    static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
-        BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
-        UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
-        assertNotNull("Unable to find Split screen divider", divider);
-        int destHeight =
-                (int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue());
-        // Drag the split screen divider to so that the ratio of top window height and bottom
-        // window height is windowHeightRatio
-        device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(),
-                device.getDisplayWidth() / 2, destHeight, 10);
-        //divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400)
-        divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
-
-        // Wait for animation to complete.
-        sleep(2000);
-    }
-
-    static void closePipWindow(UiDevice device) {
-        UiObject2 pipWindow = device.findObject(
-                By.res(SYSTEMUI_PACKAGE, "background"));
-        pipWindow.click();
-        UiObject2 exitPipObject = device.findObject(
-                By.res(SYSTEMUI_PACKAGE, "dismiss"));
-        exitPipObject.click();
-        // Wait for animation to complete.
-        sleep(2000);
-    }
-
-    static void expandPipWindow(UiDevice device) {
-        UiObject2 pipWindow = device.findObject(
-                By.res(SYSTEMUI_PACKAGE, "background"));
-        pipWindow.click();
-        pipWindow.click();
-    }
-
-    public static void stopPackage(Context context, String packageName) {
-        runShellCommand("am force-stop " + packageName);
-        int packageUid;
-        try {
-            packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0);
-        } catch (PackageManager.NameNotFoundException e) {
-            return;
-        }
-        while (targetPackageIsRunning(packageUid)) {
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                //ignore
-            }
-        }
-    }
-
-    private static boolean targetPackageIsRunning(int uid) {
-        final String result = runShellCommand(
-                String.format("cmd activity get-uid-state %d", uid));
-        return !result.contains("(NONEXISTENT)");
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
index 9525f41..c47f7f4 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
@@ -19,7 +19,7 @@
 /**
  * Common interface for Layer and WindowManager trace entries.
  */
-interface ITraceEntry {
+public interface ITraceEntry {
     /**
      * @return timestamp of current entry
      */
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
index 660ec0f..68986d4 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.surfaceflinger.nano.Layers.LayerProto;
 import android.surfaceflinger.nano.Layers.RectProto;
@@ -25,11 +24,14 @@
 import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
 import android.util.SparseArray;
 
+import androidx.annotation.Nullable;
+
 import com.android.server.wm.flicker.Assertions.Result;
 
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -57,7 +59,7 @@
      * @param data   binary proto data
      * @param source Path to source of data for additional debug information
      */
-    static LayersTrace parseFrom(byte[] data, Path source) {
+    public static LayersTrace parseFrom(byte[] data, Path source) {
         List<Entry> entries = new ArrayList<>();
         LayersTraceFileProto fileProto;
         try {
@@ -79,15 +81,15 @@
      *
      * @param data binary proto data
      */
-    static LayersTrace parseFrom(byte[] data) {
+    public static LayersTrace parseFrom(byte[] data) {
         return parseFrom(data, null);
     }
 
-    List<Entry> getEntries() {
+    public List<Entry> getEntries() {
         return mEntries;
     }
 
-    Entry getEntry(long timestamp) {
+    public Entry getEntry(long timestamp) {
         Optional<Entry> entry = mEntries.stream()
                 .filter(e -> e.getTimestamp() == timestamp)
                 .findFirst();
@@ -97,14 +99,14 @@
         return entry.get();
     }
 
-    Optional<Path> getSource() {
+    public Optional<Path> getSource() {
         return Optional.ofNullable(mSource);
     }
 
     /**
      * Represents a single Layer trace entry.
      */
-    static class Entry implements ITraceEntry {
+    public static class Entry implements ITraceEntry {
         private long mTimestamp;
         private List<Layer> mRootLayers; // hierarchical representation of layers
         private List<Layer> mFlattenedLayers = null;
@@ -117,7 +119,7 @@
         /**
          * Constructs the layer hierarchy from a flattened list of layers.
          */
-        static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
+        public static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
             SparseArray<Layer> layerMap = new SparseArray<>();
             ArrayList<Layer> orphans = new ArrayList<>();
             for (LayerProto proto : protos) {
@@ -181,7 +183,7 @@
         /**
          * Checks if a region specified by {@code testRect} is covered by all visible layers.
          */
-        Result coversRegion(Rect testRect) {
+        public Result coversRegion(Rect testRect) {
             String assertionName = "coversRegion";
             Collection<Layer> layers = asFlattenedLayers();
 
@@ -224,7 +226,7 @@
          * Checks if a layer with name {@code layerName} has a visible region
          * {@code expectedVisibleRegion}.
          */
-        Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
+        public Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
             String assertionName = "hasVisibleRegion";
             String reason = "Could not find " + layerName;
             for (Layer layer : asFlattenedLayers()) {
@@ -252,7 +254,7 @@
         /**
          * Checks if a layer with name {@code layerName} is visible.
          */
-        Result isVisible(String layerName) {
+        public Result isVisible(String layerName) {
             String assertionName = "isVisible";
             String reason = "Could not find " + layerName;
             for (Layer layer : asFlattenedLayers()) {
@@ -277,24 +279,27 @@
             return mTimestamp;
         }
 
-        List<Layer> getRootLayers() {
+        public List<Layer> getRootLayers() {
             return mRootLayers;
         }
 
-        List<Layer> asFlattenedLayers() {
+        /**
+         * Returns all layers as a flattened list using a depth first traversal.
+         */
+        public List<Layer> asFlattenedLayers() {
             if (mFlattenedLayers == null) {
-                mFlattenedLayers = new ArrayList<>();
+                mFlattenedLayers = new LinkedList<>();
                 ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
                 while (!pendingLayers.isEmpty()) {
                     Layer layer = pendingLayers.remove(0);
                     mFlattenedLayers.add(layer);
-                    pendingLayers.addAll(layer.mChildren);
+                    pendingLayers.addAll(0, layer.mChildren);
                 }
             }
             return mFlattenedLayers;
         }
 
-        Rect getVisibleBounds(String layerName) {
+        public Rect getVisibleBounds(String layerName) {
             List<Layer> layers = asFlattenedLayers();
             for (Layer layer : layers) {
                 if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
@@ -308,12 +313,12 @@
     /**
      * Represents a single layer with links to its parent and child layers.
      */
-    static class Layer {
+    public static class Layer {
         @Nullable
-        LayerProto mProto;
-        List<Layer> mChildren;
+        public LayerProto mProto;
+        public List<Layer> mChildren;
         @Nullable
-        Layer mParent = null;
+        public Layer mParent = null;
 
         private Layer(LayerProto proto) {
             this.mProto = proto;
@@ -328,16 +333,16 @@
             this.mParent = parentLayer;
         }
 
-        int getId() {
+        public int getId() {
             return mProto.id;
         }
 
-        boolean isActiveBufferEmpty() {
+        public boolean isActiveBufferEmpty() {
             return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
                     || this.mProto.activeBuffer.width == 0;
         }
 
-        boolean isVisibleRegionEmpty() {
+        public boolean isVisibleRegionEmpty() {
             if (this.mProto.visibleRegion == null) {
                 return true;
             }
@@ -345,32 +350,35 @@
             return visibleRect.height() == 0 || visibleRect.width() == 0;
         }
 
-        boolean isHidden() {
+        public boolean isHidden() {
             return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
         }
 
-        boolean isVisible() {
-            return (!isActiveBufferEmpty() || isColorLayer()) &&
-                    !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty();
+        public boolean isVisible() {
+            return (!isActiveBufferEmpty() || isColorLayer())
+                    && !isHidden()
+                    && this.mProto.color != null
+                    && this.mProto.color.a > 0
+                    && !isVisibleRegionEmpty();
         }
 
-        boolean isColorLayer() {
+        public boolean isColorLayer() {
             return this.mProto.type.equals("ColorLayer");
         }
 
-        boolean isRootLayer() {
+        public boolean isRootLayer() {
             return mParent == null || mParent.mProto == null;
         }
 
-        boolean isInvisible() {
+        public boolean isInvisible() {
             return !isVisible();
         }
 
-        boolean isHiddenByParent() {
+        public boolean isHiddenByParent() {
             return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
         }
 
-        String getHiddenByParentReason() {
+        public String getHiddenByParentReason() {
             String reason = "Layer " + mProto.name;
             if (isHiddenByParent()) {
                 reason += " is hidden by parent: " + mParent.mProto.name;
@@ -380,7 +388,7 @@
             return reason;
         }
 
-        String getVisibilityReason() {
+        public String getVisibilityReason() {
             String reason = "Layer " + mProto.name;
             if (isVisible()) {
                 reason += " is visible:";
@@ -399,7 +407,7 @@
                 if (isHidden()) {
                     reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
                 }
-                if (this.mProto.color.a == 0) {
+                if (this.mProto.color == null || this.mProto.color.a == 0) {
                     reason += " color.a=0";
                 }
                 if (isVisibleRegionEmpty()) {
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
index 4085810..4a5129e 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
@@ -19,9 +19,10 @@
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.annotation.Nullable;
 import android.graphics.Rect;
 
+import androidx.annotation.Nullable;
+
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.flicker.LayersTrace.Entry;
 import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
index 0a3fe3c..241a1c0 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
+import static com.android.server.wm.flicker.monitor.ITransitionMonitor.OUTPUT_DIR;
+
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.wm.flicker.monitor.ITransitionMonitor;
@@ -89,7 +91,7 @@
  * }
  * </pre>
  */
-class TransitionRunner {
+public class TransitionRunner {
     private static final String TAG = "FLICKER";
     private final ScreenRecorder mScreenRecorder;
     private final WindowManagerTraceMonitor mWmTraceMonitor;
@@ -128,8 +130,12 @@
         mTestTag = builder.mTestTag;
     }
 
-    static TransitionBuilder newBuilder() {
-        return new TransitionBuilder();
+    public static TransitionBuilder newBuilder() {
+        return newBuilder(OUTPUT_DIR.toString());
+    }
+
+    public static TransitionBuilder newBuilder(String outputDir) {
+        return new TransitionBuilder(outputDir);
     }
 
     /**
@@ -138,7 +144,7 @@
      *
      * @return itself
      */
-    TransitionRunner run() {
+    public TransitionRunner run() {
         mResults = new ArrayList<>();
         mAllRunsMonitors.forEach(ITransitionMonitor::start);
         mBeforeAlls.forEach(Runnable::run);
@@ -159,8 +165,7 @@
         mAfterAlls.forEach(Runnable::run);
         mAllRunsMonitors.forEach(monitor -> {
             monitor.stop();
-            Path path = monitor.save(mTestTag);
-            Log.e(TAG, "Video saved to " + path.toString());
+            monitor.save(mTestTag);
         });
         return this;
     }
@@ -170,7 +175,7 @@
      *
      * @return list of transition results.
      */
-    List<TransitionResult> getResults() {
+    public List<TransitionResult> getResults() {
         if (mResults == null) {
             throw new IllegalStateException("Results do not exist!");
         }
@@ -182,7 +187,7 @@
      *
      * @return list of transition results.
      */
-    void deleteResults() {
+    public void deleteResults() {
         if (mResults == null) {
             return;
         }
@@ -228,33 +233,33 @@
     @VisibleForTesting
     public static class TransitionResult {
         @Nullable
-        final Path layersTrace;
+        public final Path layersTrace;
         @Nullable
-        final Path windowManagerTrace;
+        public final Path windowManagerTrace;
         @Nullable
-        final Path screenCaptureVideo;
+        public final Path screenCaptureVideo;
         private boolean flaggedForSaving;
 
-        TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
+        public TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
                 @Nullable Path screenCaptureVideo) {
             this.layersTrace = layersTrace;
             this.windowManagerTrace = windowManagerTrace;
             this.screenCaptureVideo = screenCaptureVideo;
         }
 
-        void flagForSaving() {
+        public void flagForSaving() {
             flaggedForSaving = true;
         }
 
-        boolean canDelete() {
+        public boolean canDelete() {
             return !flaggedForSaving;
         }
 
-        boolean layersTraceExists() {
+        public boolean layersTraceExists() {
             return layersTrace != null && layersTrace.toFile().exists();
         }
 
-        byte[] getLayersTrace() {
+        public byte[] getLayersTrace() {
             try {
                 return Files.toByteArray(this.layersTrace.toFile());
             } catch (IOException e) {
@@ -262,11 +267,11 @@
             }
         }
 
-        Path getLayersTracePath() {
+        public Path getLayersTracePath() {
             return layersTrace;
         }
 
-        boolean windowManagerTraceExists() {
+        public boolean windowManagerTraceExists() {
             return windowManagerTrace != null && windowManagerTrace.toFile().exists();
         }
 
@@ -278,19 +283,19 @@
             }
         }
 
-        Path getWindowManagerTracePath() {
+        public Path getWindowManagerTracePath() {
             return windowManagerTrace;
         }
 
-        boolean screenCaptureVideoExists() {
+        public boolean screenCaptureVideoExists() {
             return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
         }
 
-        Path screenCaptureVideoPath() {
+        public Path screenCaptureVideoPath() {
             return screenCaptureVideo;
         }
 
-        void delete() {
+        public void delete() {
             if (layersTraceExists()) layersTrace.toFile().delete();
             if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
             if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
@@ -300,7 +305,7 @@
     /**
      * Builds a {@link TransitionRunner} instance.
      */
-    static class TransitionBuilder {
+    public static class TransitionBuilder {
         private ScreenRecorder mScreenRecorder;
         private WindowManagerTraceMonitor mWmTraceMonitor;
         private LayersTraceMonitor mLayersTraceMonitor;
@@ -323,15 +328,15 @@
 
         private boolean mRecordAllRuns = false;
 
-        TransitionBuilder() {
+        public TransitionBuilder(String outputDir) {
             mScreenRecorder = new ScreenRecorder();
-            mWmTraceMonitor = new WindowManagerTraceMonitor();
-            mLayersTraceMonitor = new LayersTraceMonitor();
+            mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir);
+            mLayersTraceMonitor = new LayersTraceMonitor(outputDir);
             mFrameStatsMonitor = new
                     WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation());
         }
 
-        TransitionRunner build() {
+        public TransitionRunner build() {
             if (mCaptureWindowManagerTrace) {
                 mPerRunMonitors.add(mWmTraceMonitor);
             }
@@ -355,52 +360,52 @@
             return new TransitionRunner(this);
         }
 
-        TransitionBuilder runBeforeAll(Runnable runnable) {
+        public TransitionBuilder runBeforeAll(Runnable runnable) {
             mBeforeAlls.add(runnable);
             return this;
         }
 
-        TransitionBuilder runBefore(Runnable runnable) {
+        public TransitionBuilder runBefore(Runnable runnable) {
             mBefores.add(runnable);
             return this;
         }
 
-        TransitionBuilder run(Runnable runnable) {
+        public TransitionBuilder run(Runnable runnable) {
             mTransitions.add(runnable);
             return this;
         }
 
-        TransitionBuilder runAfter(Runnable runnable) {
+        public TransitionBuilder runAfter(Runnable runnable) {
             mAfters.add(runnable);
             return this;
         }
 
-        TransitionBuilder runAfterAll(Runnable runnable) {
+        public TransitionBuilder runAfterAll(Runnable runnable) {
             mAfterAlls.add(runnable);
             return this;
         }
 
-        TransitionBuilder repeat(int iterations) {
+        public TransitionBuilder repeat(int iterations) {
             mIterations = iterations;
             return this;
         }
 
-        TransitionBuilder skipWindowManagerTrace() {
+        public TransitionBuilder skipWindowManagerTrace() {
             mCaptureWindowManagerTrace = false;
             return this;
         }
 
-        TransitionBuilder skipLayersTrace() {
+        public TransitionBuilder skipLayersTrace() {
             mCaptureLayersTrace = false;
             return this;
         }
 
-        TransitionBuilder includeJankyRuns() {
+        public TransitionBuilder includeJankyRuns() {
             mRunJankFree = false;
             return this;
         }
 
-        TransitionBuilder recordEachRun() {
+        public TransitionBuilder recordEachRun() {
             if (mRecordAllRuns) {
                 throw new IllegalArgumentException("Invalid option with recordAllRuns");
             }
@@ -408,7 +413,7 @@
             return this;
         }
 
-        TransitionBuilder recordAllRuns() {
+        public TransitionBuilder recordAllRuns() {
             if (mRecordEachRun) {
                 throw new IllegalArgumentException("Invalid option with recordEachRun");
             }
@@ -416,7 +421,11 @@
             return this;
         }
 
-        TransitionBuilder withTag(String testTag) {
+        public TransitionBuilder withTag(String testTag) {
+            if (testTag.contains(" ")) {
+                throw new IllegalArgumentException("The test tag can not contain spaces since it "
+                        + "is a part of the file name");
+            }
             mTestTag = testTag;
             return this;
         }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
index e3592eb..412e72d8 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.nano.AppWindowTokenProto;
@@ -58,7 +58,7 @@
      * @param data   binary proto data
      * @param source Path to source of data for additional debug information
      */
-    static WindowManagerTrace parseFrom(byte[] data, Path source) {
+    public static WindowManagerTrace parseFrom(byte[] data, Path source) {
         List<Entry> entries = new ArrayList<>();
 
         WindowManagerTraceFileProto fileProto;
@@ -73,7 +73,7 @@
         return new WindowManagerTrace(entries, source);
     }
 
-    static WindowManagerTrace parseFrom(byte[] data) {
+    public static WindowManagerTrace parseFrom(byte[] data) {
         return parseFrom(data, null);
     }
 
@@ -81,7 +81,7 @@
         return mEntries;
     }
 
-    Entry getEntry(long timestamp) {
+    public Entry getEntry(long timestamp) {
         Optional<Entry> entry = mEntries.stream()
                 .filter(e -> e.getTimestamp() == timestamp)
                 .findFirst();
@@ -91,17 +91,17 @@
         return entry.get();
     }
 
-    Optional<Path> getSource() {
+    public Optional<Path> getSource() {
         return Optional.ofNullable(mSource);
     }
 
     /**
      * Represents a single WindowManager trace entry.
      */
-    static class Entry implements ITraceEntry {
+    public static class Entry implements ITraceEntry {
         private final WindowManagerTraceProto mProto;
 
-        Entry(WindowManagerTraceProto proto) {
+        public Entry(WindowManagerTraceProto proto) {
             mProto = proto;
         }
 
@@ -162,7 +162,7 @@
         /**
          * Checks if aboveAppWindow with {@code windowTitle} is visible.
          */
-        Result isAboveAppWindowVisible(String windowTitle) {
+        public Result isAboveAppWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].aboveAppWindows;
@@ -173,7 +173,7 @@
         /**
          * Checks if belowAppWindow with {@code windowTitle} is visible.
          */
-        Result isBelowAppWindowVisible(String windowTitle) {
+        public Result isBelowAppWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].belowAppWindows;
@@ -185,7 +185,7 @@
         /**
          * Checks if imeWindow with {@code windowTitle} is visible.
          */
-        Result isImeWindowVisible(String windowTitle) {
+        public Result isImeWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].imeWindows;
@@ -197,7 +197,7 @@
         /**
          * Checks if app window with {@code windowTitle} is on top.
          */
-        Result isVisibleAppWindowOnTop(String windowTitle) {
+        public Result isVisibleAppWindowOnTop(String windowTitle) {
             String topAppWindow = getTopVisibleAppWindow();
             boolean success = topAppWindow.contains(windowTitle);
             String reason = "wanted=" + windowTitle + " found=" + topAppWindow;
@@ -207,7 +207,7 @@
         /**
          * Checks if app window with {@code windowTitle} is visible.
          */
-        Result isAppWindowVisible(String windowTitle) {
+        public Result isAppWindowVisible(String windowTitle) {
             final String assertionName = "isAppWindowVisible";
             boolean titleFound = false;
             StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
index c54396f..3d25fbe 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
@@ -28,9 +28,9 @@
 /**
  * Helper functions to retrieve system window sizes and positions.
  */
-class WindowUtils {
+public class WindowUtils {
 
-    static Rect getDisplayBounds() {
+    public static Rect getDisplayBounds() {
         Point display = new Point();
         WindowManager wm =
                 (WindowManager) InstrumentationRegistry.getContext().getSystemService(
@@ -46,7 +46,7 @@
         return wm.getDefaultDisplay().getRotation();
     }
 
-    static Rect getDisplayBounds(int requestedRotation) {
+    public static Rect getDisplayBounds(int requestedRotation) {
         Rect displayBounds = getDisplayBounds();
         int currentDisplayRotation = getCurrentRotation();
 
@@ -66,7 +66,7 @@
     }
 
 
-    static Rect getAppPosition(int requestedRotation) {
+    public static Rect getAppPosition(int requestedRotation) {
         Rect displayBounds = getDisplayBounds();
         int currentDisplayRotation = getCurrentRotation();
 
@@ -85,7 +85,7 @@
         return new Rect(0, 0, displayBounds.width(), displayBounds.height());
     }
 
-    static Rect getStatusBarPosition(int requestedRotation) {
+    public static Rect getStatusBarPosition(int requestedRotation) {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         String resourceName;
         Rect displayBounds = getDisplayBounds();
@@ -104,7 +104,7 @@
         return new Rect(0, 0, width, height);
     }
 
-    static Rect getNavigationBarPosition(int requestedRotation) {
+    public static Rect getNavigationBarPosition(int requestedRotation) {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         Rect displayBounds = getDisplayBounds();
         int displayWidth = Math.min(displayBounds.width(), displayBounds.height());
@@ -129,13 +129,13 @@
         }
     }
 
-    static int getNavigationBarHeight() {
+    public static int getNavigationBarHeight() {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
         return resources.getDimensionPixelSize(resourceId);
     }
 
-    static int getDockedStackDividerInset() {
+    public static int getDockedStackDividerInset() {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen",
                 "android");
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
index e76da6e..064cc27 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
@@ -19,7 +19,7 @@
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java
new file mode 100644
index 0000000..6821ff0
--- /dev/null
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers;
+
+import static android.os.SystemClock.sleep;
+import static android.system.helpers.OverviewHelper.isRecentsInLauncher;
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.support.test.launcherhelper.LauncherStrategyFactory;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.util.Rational;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.wm.flicker.WindowUtils;
+
+/**
+ * Collection of UI Automation helper functions.
+ */
+public class AutomationUtils {
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final long FIND_TIMEOUT = 10000;
+    private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L;
+    private static final String TAG = "FLICKER";
+
+    public static void wakeUpAndGoToHomeScreen() {
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry
+                .getInstrumentation());
+        try {
+            device.wakeUp();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        device.pressHome();
+    }
+
+    /**
+     * Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing
+     * the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly.
+     * This removes some delays when using the UIAutomator library required to create fast UI
+     * transitions.
+     */
+    public static void setFastWait() {
+        Configurator.getInstance().setWaitForIdleTimeout(0);
+    }
+
+    /**
+     * Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior.
+     */
+    public static void setDefaultWait() {
+        Configurator.getInstance().setWaitForIdleTimeout(10000);
+    }
+
+    public static boolean isQuickstepEnabled(UiDevice device) {
+        return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null;
+    }
+
+    public static void openQuickstep(UiDevice device) {
+        if (isQuickstepEnabled(device)) {
+            int height = device.getDisplayHeight();
+            UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"));
+
+            Rect navBarVisibleBounds;
+
+            // TODO(vishnun) investigate why this object cannot be found.
+            if (navBar != null) {
+                navBarVisibleBounds = navBar.getVisibleBounds();
+            } else {
+                Log.e(TAG, "Could not find nav bar, infer location");
+                navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0);
+            }
+
+            // Swipe from nav bar to 2/3rd down the screen.
+            device.swipe(
+                    navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(),
+                    navBarVisibleBounds.centerX(), height * 2 / 3,
+                    (navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step
+        } else {
+            try {
+                device.pressRecentApps();
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view");
+
+        // use a long timeout to wait until recents populated
+        if (device.wait(
+                Until.findObject(isRecentsInLauncher()
+                        ? getLauncherOverviewSelector(device) : RECENTS),
+                10000) == null) {
+            fail("Recents didn't appear");
+        }
+        device.waitForIdle();
+    }
+
+    public static void clearRecents(UiDevice device) {
+        if (isQuickstepEnabled(device)) {
+            openQuickstep(device);
+
+            for (int i = 0; i < 5; i++) {
+                device.swipe(device.getDisplayWidth() / 2,
+                        device.getDisplayHeight() / 2, device.getDisplayWidth(),
+                        device.getDisplayHeight() / 2,
+                        5);
+
+                BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher",
+                        "clear_all_button");
+                UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100);
+                if (clearAllButton != null) {
+                    clearAllButton.click();
+                    return;
+                }
+            }
+        }
+    }
+
+    private static BySelector getLauncherOverviewSelector(UiDevice device) {
+        return By.res(device.getLauncherPackageName(), "overview_panel");
+    }
+
+    private static void longPressRecents(UiDevice device) {
+        BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps");
+        UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT);
+        assertNotNull("Unable to find recents button", recentsButton);
+        recentsButton.click(LONG_PRESS_TIMEOUT);
+    }
+
+    public static void launchSplitScreen(UiDevice device) {
+        String mLauncherPackage = LauncherStrategyFactory.getInstance(device)
+                .getLauncherStrategy().getSupportedLauncherPackage();
+
+        if (isQuickstepEnabled(device)) {
+            // Quickstep enabled
+            openQuickstep(device);
+
+            BySelector overviewIconSelector = By.res(mLauncherPackage, "icon")
+                    .clazz(View.class);
+            UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector),
+                    FIND_TIMEOUT);
+            assertNotNull("Unable to find app icon in Overview", overviewIcon);
+            overviewIcon.click();
+
+            BySelector splitscreenButtonSelector = By.text("Split screen");
+            UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector),
+                    FIND_TIMEOUT);
+            assertNotNull("Unable to find Split screen button in Overview", splitscreenButton);
+            splitscreenButton.click();
+        } else {
+            // Classic long press recents
+            longPressRecents(device);
+        }
+        // Wait for animation to complete.
+        sleep(2000);
+    }
+
+    public static void exitSplitScreen(UiDevice device) {
+        if (isQuickstepEnabled(device)) {
+            // Quickstep enabled
+            BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
+            UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
+            assertNotNull("Unable to find Split screen divider", divider);
+
+            // Drag the split screen divider to the top of the screen
+            divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400);
+        } else {
+            // Classic long press recents
+            longPressRecents(device);
+        }
+        // Wait for animation to complete.
+        sleep(2000);
+    }
+
+    public static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
+        BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
+        UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
+        assertNotNull("Unable to find Split screen divider", divider);
+        int destHeight =
+                (int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue());
+        // Drag the split screen divider to so that the ratio of top window height and bottom
+        // window height is windowHeightRatio
+        device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(),
+                device.getDisplayWidth() / 2, destHeight, 10);
+        //divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400)
+        divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
+
+        // Wait for animation to complete.
+        sleep(2000);
+    }
+
+    public static void closePipWindow(UiDevice device) {
+        UiObject2 pipWindow = device.findObject(
+                By.res(SYSTEMUI_PACKAGE, "background"));
+        pipWindow.click();
+        UiObject2 exitPipObject = device.findObject(
+                By.res(SYSTEMUI_PACKAGE, "dismiss"));
+        exitPipObject.click();
+        // Wait for animation to complete.
+        sleep(2000);
+    }
+
+    public static void expandPipWindow(UiDevice device) {
+        UiObject2 pipWindow = device.findObject(
+                By.res(SYSTEMUI_PACKAGE, "background"));
+        pipWindow.click();
+        pipWindow.click();
+    }
+
+    public static void stopPackage(Context context, String packageName) {
+        runShellCommand("am force-stop " + packageName);
+        int packageUid;
+        try {
+            packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return;
+        }
+        while (targetPackageIsRunning(packageUid)) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                //ignore
+            }
+        }
+    }
+
+    private static boolean targetPackageIsRunning(int uid) {
+        final String result = runShellCommand(
+                String.format("cmd activity get-uid-state %d", uid));
+        return !result.contains("(NONEXISTENT)");
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
index c55d068..da75b3e 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
@@ -16,21 +16,22 @@
 
 package com.android.server.wm.flicker.monitor;
 
-import android.os.IBinder;
-import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
 
 /**
  * Captures Layers trace from SurfaceFlinger.
  */
 public class LayersTraceMonitor extends TraceMonitor {
-    private static final String TAG = "LayersTraceMonitor";
-    private IBinder mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+    private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
 
     public LayersTraceMonitor() {
-        traceFileName = "layers_trace.pb";
+        this(OUTPUT_DIR.toString());
+    }
+
+    public LayersTraceMonitor(String outputDir) {
+        super(outputDir, "layers_trace.pb");
     }
 
     @Override
@@ -45,30 +46,19 @@
 
     @Override
     public boolean isEnabled() throws RemoteException {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken("android.ui.ISurfaceComposer");
-        mSurfaceFlinger.transact(/* LAYER_TRACE_STATUS_CODE */ 1026,
-                data, reply, 0 /* flags */);
-        return reply.readBoolean();
+        try {
+            return mWm.isLayerTracing();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return false;
     }
 
     private void setEnabled(boolean isEnabled) {
-        Parcel data = null;
         try {
-            if (mSurfaceFlinger != null) {
-                data = Parcel.obtain();
-                data.writeInterfaceToken("android.ui.ISurfaceComposer");
-                data.writeInt(isEnabled ? 1 : 0);
-                mSurfaceFlinger.transact( /* LAYER_TRACE_CONTROL_CODE */ 1025,
-                        data, null, 0 /* flags */);
-            }
+            mWm.setLayerTracing(isEnabled);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not set layer tracing." + e.toString());
-        } finally {
-            if (data != null) {
-                data.recycle();
-            }
+            e.printStackTrace();
         }
     }
 }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
index 4787586..dce1c27 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
@@ -20,25 +20,25 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 
 /**
  * Captures screen contents and saves it as a mp4 video file.
  */
 public class ScreenRecorder implements ITransitionMonitor {
     @VisibleForTesting
-    static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
+    public static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
     private static final String TAG = "FLICKER";
     private Thread recorderThread;
 
     @VisibleForTesting
-    static Path getPath(String testTag) {
+    public static Path getPath(String testTag) {
         return OUTPUT_DIR.resolve(testTag + ".mp4");
     }
 
@@ -69,8 +69,10 @@
     @Override
     public Path save(String testTag) {
         try {
-            return Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
+            Path targetPath = Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
                     REPLACE_EXISTING);
+            Log.i(TAG, "Video saved to " + targetPath.toString());
+            return targetPath;
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
index 0e154ec..1ba36bb 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
@@ -20,7 +20,7 @@
 
 import android.os.RemoteException;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
 
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -34,9 +34,15 @@
     public static final String TAG = "FLICKER";
     private static final String TRACE_DIR = "/data/misc/wmtrace/";
 
-    String traceFileName;
+    private Path mOutputDir;
+    public String mTraceFileName;
 
-    abstract boolean isEnabled() throws RemoteException;
+    public abstract boolean isEnabled() throws RemoteException;
+
+    public TraceMonitor(String outputDir, String traceFileName) {
+        mOutputDir = Paths.get(outputDir);
+        mTraceFileName = traceFileName;
+    }
 
     /**
      * Saves trace file to the external storage directory suffixing the name with the testtag
@@ -53,14 +59,16 @@
     public Path save(String testTag) {
         OUTPUT_DIR.toFile().mkdirs();
         Path traceFileCopy = getOutputTraceFilePath(testTag);
+
+        // Read the input stream fully.
         String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR,
-                traceFileName, traceFileCopy.toString());
+                mTraceFileName, traceFileCopy.toString());
         runShellCommand(copyCommand);
         return traceFileCopy;
     }
 
     @VisibleForTesting
-    Path getOutputTraceFilePath(String testTag) {
-        return OUTPUT_DIR.resolve(traceFileName + "_" + testTag);
+    public Path getOutputTraceFilePath(String testTag) {
+        return mOutputDir.resolve(mTraceFileName + "_" + testTag);
     }
 }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
index ae160b68..11de4aa 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
@@ -24,16 +24,20 @@
  * Captures WindowManager trace from WindowManager.
  */
 public class WindowManagerTraceMonitor extends TraceMonitor {
-    private IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+    private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
 
     public WindowManagerTraceMonitor() {
-        traceFileName = "wm_trace.pb";
+        this(OUTPUT_DIR.toString());
+    }
+
+    public WindowManagerTraceMonitor(String outputDir) {
+        super(outputDir, "wm_trace.pb");
     }
 
     @Override
     public void start() {
         try {
-            wm.startWindowTrace();
+            mWm.startWindowTrace();
         } catch (RemoteException e) {
             throw new RuntimeException("Could not start trace", e);
         }
@@ -42,7 +46,7 @@
     @Override
     public void stop() {
         try {
-            wm.stopWindowTrace();
+            mWm.stopWindowTrace();
         } catch (RemoteException e) {
             throw new RuntimeException("Could not stop trace", e);
         }
@@ -50,6 +54,6 @@
 
     @Override
     public boolean isEnabled() throws RemoteException{
-        return wm.isWindowTraceEnabled();
+        return mWm.isWindowTraceEnabled();
     }
 }
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
index dd6fed0..f312384 100644
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
+++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.monitor;
 
-import static com.android.server.wm.flicker.AutomationUtils.wakeUpAndGoToHomeScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.wakeUpAndGoToHomeScreen;
 
 import androidx.test.InstrumentationRegistry;
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
index 65888ac..5cf2c1c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
@@ -19,12 +19,12 @@
 import static android.os.SystemClock.sleep;
 import static android.view.Surface.rotationToString;
 
-import static com.android.server.wm.flicker.AutomationUtils.clearRecents;
-import static com.android.server.wm.flicker.AutomationUtils.closePipWindow;
-import static com.android.server.wm.flicker.AutomationUtils.exitSplitScreen;
-import static com.android.server.wm.flicker.AutomationUtils.expandPipWindow;
-import static com.android.server.wm.flicker.AutomationUtils.launchSplitScreen;
-import static com.android.server.wm.flicker.AutomationUtils.stopPackage;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.clearRecents;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.closePipWindow;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.expandPipWindow;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.launchSplitScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.stopPackage;
 
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
+import com.android.server.wm.flicker.helpers.AutomationUtils;
 
 /**
  * Collection of common transitions which can be used to test different apps or scenarios.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
index 00e11c0..8c9d6b4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker;
 
-import static com.android.server.wm.flicker.AutomationUtils.setDefaultWait;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 88d92c4..0b75039 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -23,6 +23,7 @@
         "androidx.test.rules",
         "services.core",
         "services.net",
+        "truth-prebuilt",
     ],
     libs: ["android.test.runner"],
     jni_libs: [
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c42201f..79f5095 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,8 @@
 
 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -44,6 +42,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.server.PackageWatchdog.HealthCheckState;
 import com.android.server.PackageWatchdog.MonitoredPackage;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -65,9 +64,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
-// TODO: Write test without using PackageWatchdog#getPackages. Just rely on
-// behavior of observers receiving crash notifications or not to determine if it's registered
-// TODO: Use Truth in tests.
 /**
  * Test PackageWatchdog.
  */
@@ -83,6 +79,7 @@
     private static final String OBSERVER_NAME_4 = "observer4";
     private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
     private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+    private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
     private Context mSpyContext;
     @Mock
@@ -114,78 +111,105 @@
         dropShellPermissions();
     }
 
-    /**
-     * Test registration, unregistration, package expiry and duration reduction
-     */
     @Test
-    public void testRegistration() throws Exception {
+    public void testRegistration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // The failed packages should be the same as the registered ones to ensure registration is
+        // done successfully
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    @Test
+    public void testRegistration_multiObservers() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
 
-        // Start observing for observer1 which will be unregistered
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        // Start observing for observer2 which will expire
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
-        // Start observing for observer3 which will have expiry duration reduced
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE)));
 
-        // Verify packages observed at start
-        // 1
-        assertEquals(1, watchdog.getPackages(observer1).size());
-        assertTrue(watchdog.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog.getPackages(observer2).size());
-        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+        // The failed packages should be the same as the registered ones to ensure registration is
+        // done successfully
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
+    }
 
-        // Then unregister observer1
-        watchdog.unregisterHealthObserver(observer1);
+    @Test
+    public void testUnregistration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        // Verify observer2 and observer3 left
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertEquals(2, watchdog.getPackages(observer2).size());
-        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.unregisterHealthObserver(observer);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
-        // Then advance time a little and run messages in Handlers so observer2 expires
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        // We should have no failed packages to ensure unregistration is done successfully
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
 
-        // Verify observer3 left with reduced expiry duration
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertNull(watchdog.getPackages(observer2));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+    @Test
+    public void testUnregistration_multiObservers() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        // Then advance time some more and run messages in Handlers so observer3 expires
-        Thread.sleep(LONG_DURATION);
-        mTestLooper.dispatchAll();
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.unregisterHealthObserver(observer2);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
-        // Verify observer3 expired
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertNull(watchdog.getPackages(observer2));
-        // 3
-        assertNull(watchdog.getPackages(observer3));
+        // observer1 should receive failed packages as intended.
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        // observer2 should have no failed packages to ensure unregistration is done successfully
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    @Test
+    public void testExpiration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        moveTimeForwardAndDispatch(SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should have no failed packages for the fatal failure is raised after expiration
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    @Test
+    public void testExpiration_multiObservers() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+        moveTimeForwardAndDispatch(SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should have no failed packages for the fatal failure is raised after expiration
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        // We should have failed packages since observer2 hasn't expired
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     /** Observing already observed package extends the observation time. */
     @Test
-    public void testObserveAlreadyObservedPackage() throws Exception {
+    public void testObserveAlreadyObservedPackage() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
@@ -193,66 +217,47 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time half-way
-        Thread.sleep(SHORT_DURATION / 2);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION / 2);
 
         // Start observing APP_A again
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time such that it should have expired were it not for the second observation
-        Thread.sleep((SHORT_DURATION / 2) + 1);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
 
-        // Verify that APP_A not expired since second observation extended the time
-        assertEquals(1, watchdog.getPackages(observer).size());
-        assertTrue(watchdog.getPackages(observer).contains(APP_A));
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // Verify that we receive failed packages as expected for APP_A not expired
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     /**
      * Test package observers are persisted and loaded on startup
      */
     @Test
-    public void testPersistence() throws Exception {
+    public void testPersistence() {
         PackageWatchdog watchdog1 = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
         watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
-
-        // Verify 2 observers are registered and saved internally
-        // 1
-        assertEquals(1, watchdog1.getPackages(observer1).size());
-        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog1.getPackages(observer2).size());
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
-
         // Then advance time and run IO Handler so file is saved
         mTestLooper.dispatchAll();
-
         // Then start a new watchdog
         PackageWatchdog watchdog2 = createWatchdog();
-
-        // Verify the new watchdog loads observers on startup but nothing registered
-        assertEquals(0, watchdog2.getPackages(observer1).size());
-        assertEquals(0, watchdog2.getPackages(observer2).size());
-        // Verify random observer not saved returns null
-        assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
-
-        // Then register observer1
+        // Then resume observer1 and observer2
         watchdog2.registerHealthObserver(observer1);
         watchdog2.registerHealthObserver(observer2);
+        raiseFatalFailureAndDispatch(watchdog2,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE)));
 
-        // Verify 2 observers are registered after reload
-        // 1
-        assertEquals(1, watchdog1.getPackages(observer1).size());
-        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog1.getPackages(observer2).size());
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+        // We should receive failed packages as expected to ensure observers are persisted and
+        // resumed correctly
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
     }
 
     /**
@@ -276,8 +281,8 @@
         mTestLooper.dispatchAll();
 
         // Verify that observers are not notified
-        assertEquals(0, observer1.mFailedPackages.size());
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
     }
 
     /**
@@ -295,16 +300,12 @@
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
 
         // Then fail APP_C (not observed) above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
 
         // Verify that observers are not notified
-        assertEquals(0, observer1.mFailedPackages.size());
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
     }
 
     /**
@@ -329,16 +330,11 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A (different version) above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(
-                            new VersionedPackage(APP_A, differentVersionCode)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)));
 
         // Verify that observers are not notified
-        assertEquals(0, observer.mFailedPackages.size());
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
     }
 
 
@@ -368,33 +364,26 @@
                 SHORT_DURATION);
 
         // Then fail all apps above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
-                    new VersionedPackage(APP_B, VERSION_CODE),
-                    new VersionedPackage(APP_C, VERSION_CODE),
-                    new VersionedPackage(APP_D, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE),
+                        new VersionedPackage(APP_C, VERSION_CODE),
+                        new VersionedPackage(APP_D, VERSION_CODE)));
 
         // Verify least impact observers are notifed of package failures
-        List<String> observerNonePackages = observerNone.mFailedPackages;
-        List<String> observerHighPackages = observerHigh.mFailedPackages;
-        List<String> observerMidPackages = observerMid.mFailedPackages;
-        List<String> observerLowPackages = observerLow.mFailedPackages;
+        List<String> observerNonePackages = observerNone.mMitigatedPackages;
+        List<String> observerHighPackages = observerHigh.mMitigatedPackages;
+        List<String> observerMidPackages = observerMid.mMitigatedPackages;
+        List<String> observerLowPackages = observerLow.mMitigatedPackages;
 
         // APP_D failure observed by only observerNone is not caught cos its impact is none
-        assertEquals(0, observerNonePackages.size());
+        assertThat(observerNonePackages).isEmpty();
         // APP_C failure is caught by observerHigh cos it's the lowest impact observer
-        assertEquals(1, observerHighPackages.size());
-        assertEquals(APP_C, observerHighPackages.get(0));
+        assertThat(observerHighPackages).containsExactly(APP_C);
         // APP_B failure is caught by observerMid cos it's the lowest impact observer
-        assertEquals(1, observerMidPackages.size());
-        assertEquals(APP_B, observerMidPackages.get(0));
+        assertThat(observerMidPackages).containsExactly(APP_B);
         // APP_A failure is caught by observerLow cos it's the lowest impact observer
-        assertEquals(1, observerLowPackages.size());
-        assertEquals(APP_A, observerLowPackages.get(0));
+        assertThat(observerLowPackages).containsExactly(APP_A);
     }
 
     /**
@@ -421,66 +410,51 @@
         watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then fail APP_A above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerFirst is notifed
-        assertEquals(1, observerFirst.mFailedPackages.size());
-        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, next action it has is high impact
         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerSecond is notifed cos it has least impact
-        assertEquals(1, observerSecond.mFailedPackages.size());
-        assertEquals(APP_A, observerSecond.mFailedPackages.get(0));
-        assertEquals(0, observerFirst.mFailedPackages.size());
+        assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerFirst.mMitigatedPackages).isEmpty();
 
         // After observerSecond handles failure, it has no further actions
         observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerFirst is notifed cos it has the only action
-        assertEquals(1, observerFirst.mFailedPackages.size());
-        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, it too has no further actions
         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify no observer is notified cos no actions left
-        assertEquals(0, observerFirst.mFailedPackages.size());
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).isEmpty();
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
     }
 
     /**
@@ -499,17 +473,12 @@
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only one observer is notifed
-        assertEquals(1, observer1.mFailedPackages.size());
-        assertEquals(APP_A, observer1.mFailedPackages.get(0));
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observer2.mMitigatedPackages).isEmpty();
     }
 
     /**
@@ -538,9 +507,7 @@
 
         // Verify we requested health checks for APP_A and APP_B
         List<String> requestedPackages = controller.getRequestedPackages();
-        assertEquals(2, requestedPackages.size());
-        assertEquals(APP_A, requestedPackages.get(0));
-        assertEquals(APP_B, requestedPackages.get(1));
+        assertThat(requestedPackages).containsExactly(APP_A, APP_B);
 
         // Then health check passed for APP_A (observer1 is aware)
         controller.setPackagePassed(APP_A);
@@ -552,23 +519,19 @@
         watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then expire observers
-        Thread.sleep(SHORT_DURATION);
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify we cancelled all requests on expiry
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Verify observer1 is not notified
-        assertEquals(0, observer1.mFailedPackages.size());
+        assertThat(observer1.mMitigatedPackages).isEmpty();
 
         // Verify observer2 is notifed because health checks for APP_B never passed
-        assertEquals(1, observer2.mFailedPackages.size());
-        assertEquals(APP_B, observer2.mFailedPackages.get(0));
+        assertThat(observer2.mMitigatedPackages).containsExactly(APP_B);
 
         // Verify observer3 is notifed because health checks for APP_A did not pass before expiry
-        assertEquals(1, observer3.mFailedPackages.size());
-        assertEquals(APP_A, observer3.mFailedPackages.get(0));
+        assertThat(observer3.mMitigatedPackages).containsExactly(APP_A);
     }
 
     /**
@@ -595,9 +558,7 @@
 
         // Verify we requested health checks for APP_A and APP_B
         List<String> requestedPackages = controller.getRequestedPackages();
-        assertEquals(2, requestedPackages.size());
-        assertEquals(APP_A, requestedPackages.get(0));
-        assertEquals(APP_B, requestedPackages.get(1));
+        assertThat(requestedPackages).containsExactly(APP_A, APP_B);
 
         // Disable explicit health checks (marks APP_A and APP_B as passed)
         setExplicitHealthCheckEnabled(false);
@@ -606,14 +567,13 @@
         mTestLooper.dispatchAll();
 
         // Verify all checks are cancelled
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Then expire APP_A
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify APP_A is not failed (APP_B) is not expired yet
-        assertEquals(0, observer.mFailedPackages.size());
+        assertThat(observer.mMitigatedPackages).isEmpty();
 
         // Re-enable explicit health checks
         setExplicitHealthCheckEnabled(true);
@@ -622,7 +582,7 @@
         mTestLooper.dispatchAll();
 
         // Verify no requests are made cos APP_A is expired and APP_B was marked as passed
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Then set new supported packages
         controller.setSupportedPackages(Arrays.asList(APP_C));
@@ -634,16 +594,13 @@
 
         // Verify requests are only made for APP_C
         requestedPackages = controller.getRequestedPackages();
-        assertEquals(1, requestedPackages.size());
-        assertEquals(APP_C, requestedPackages.get(0));
+        assertThat(requestedPackages).containsExactly(APP_C);
 
         // Then expire APP_A and APP_C
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify only APP_C is failed because explicit health checks was not supported for APP_A
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_C, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_C);
     }
 
     /**
@@ -664,21 +621,19 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then APP_A has exceeded health check duration
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify that health check is failed
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_A, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
 
         // Then clear failed packages and start observing a random package so requests are synced
         // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again
         // this time due to package expiry.
-        observer.mFailedPackages.clear();
+        observer.mMitigatedPackages.clear();
         watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
 
         // Verify that health check failure is not notified again
-        assertTrue(observer.mFailedPackages.isEmpty());
+        assertThat(observer.mMitigatedPackages).isEmpty();
     }
 
     /** Tests {@link MonitoredPackage} health check state transitions. */
@@ -694,36 +649,38 @@
 
         // Verify transition: inactive -> active -> passed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked());
+        assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify still inactive, until we #setHealthCheckActiveLocked
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now active
-        assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION));
+        assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+                HealthCheckState.ACTIVE);
         // Verify now passed
-        assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked());
+        assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED);
 
         // Verify transition: inactive -> active -> failed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked());
+        assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now active
-        assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION));
+        assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+                HealthCheckState.ACTIVE);
         // Verify now failed
-        assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED);
 
         // Verify transition: inactive -> failed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked());
+        assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now failed because package expired
-        assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION));
+        assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED);
         // Verify remains failed even when asked to pass
-        assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked());
+        assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED);
 
         // Verify transition: passed
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked());
+        assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED);
         // Verify remains passed even if health check fails
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED);
         // Verify remains passed even if package expires
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION));
+        assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED);
     }
 
     @Test
@@ -742,8 +699,142 @@
         mTestLooper.dispatchAll();
 
         // Verify the NetworkStack observer is notified
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_A, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
+    }
+
+    /** Test default values are used when device property is invalid. */
+    @Test
+    public void testInvalidConfig_watchdogTriggerFailureCount() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(-1), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        // Fail APP_A below the threshold which should not trigger package failures
+        for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
+            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        }
+        mTestLooper.dispatchAll();
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+        // One more to trigger the package failure
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /** Test default values are used when device property is invalid. */
+    @Test
+    public void testInvalidConfig_watchdogTriggerDurationMillis() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(2), /*makeDefault*/false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+                Integer.toString(-1), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We shouldn't receive APP_A since the interval of 2 failures is greater than
+        // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We should receive APP_B since the interval of 2 failures is less than
+        // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B);
+    }
+
+    /**
+     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+     * an invalid durationMs.
+     */
+    @Test
+    public void testInvalidMonitoringDuration_beforeExpiry() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
+        // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
+        // small timeouts.
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should receive APP_A since the observer hasn't expired
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /**
+     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+     * an invalid durationMs.
+     */
+    @Test
+    public void testInvalidMonitoringDuration_afterExpiry() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should receive nothing since the observer has expired
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    /** Test we are notified when enough failures are triggered within any window. */
+    @Test
+    public void testFailureTriggerWindow() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(3), /*makeDefault*/false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+                Integer.toString(1000), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+        // Raise 2 failures at t=0 and t=900 respectively
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(900);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // Raise 2 failures at t=1100
+        moveTimeForwardAndDispatch(200);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We should receive APP_A since there are 3 failures within 1000ms window
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     private void adoptShellPermissions(String... permissions) {
@@ -772,6 +863,21 @@
         }
     }
 
+    private void moveTimeForwardAndDispatch(long milliSeconds) {
+        mTestClock.moveTimeForward(milliSeconds);
+        mTestLooper.moveTimeForward(milliSeconds);
+        mTestLooper.dispatchAll();
+    }
+
+    /** Trigger package failures above the threshold. */
+    private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+            List<VersionedPackage> packages) {
+        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+            watchdog.onPackageFailure(packages);
+        }
+        mTestLooper.dispatchAll();
+    }
+
     private PackageWatchdog createWatchdog() {
         return createWatchdog(new TestController(), true /* withPackagesReady */);
     }
@@ -782,15 +888,15 @@
         Handler handler = new Handler(mTestLooper.getLooper());
         PackageWatchdog watchdog =
                 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
-                        mConnectivityModuleConnector);
+                        mConnectivityModuleConnector, mTestClock);
         // Verify controller is not automatically started
-        assertFalse(controller.mIsEnabled);
+        assertThat(controller.mIsEnabled).isFalse();
         if (withPackagesReady) {
             // Only capture the NetworkStack callback for the latest registered watchdog
             reset(mConnectivityModuleConnector);
             watchdog.onPackagesReady();
             // Verify controller by default is started when packages are ready
-            assertTrue(controller.mIsEnabled);
+            assertThat(controller.mIsEnabled).isTrue();
 
             verify(mConnectivityModuleConnector).registerHealthListener(
                     mConnectivityModuleCallbackCaptor.capture());
@@ -801,7 +907,8 @@
     private static class TestObserver implements PackageHealthObserver {
         private final String mName;
         private int mImpact;
-        final List<String> mFailedPackages = new ArrayList<>();
+        final List<String> mHealthCheckFailedPackages = new ArrayList<>();
+        final List<String> mMitigatedPackages = new ArrayList<>();
 
         TestObserver(String name) {
             mName = name;
@@ -814,11 +921,12 @@
         }
 
         public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+            mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
             return mImpact;
         }
 
         public boolean execute(VersionedPackage versionedPackage) {
-            mFailedPackages.add(versionedPackage.getPackageName());
+            mMitigatedPackages.add(versionedPackage.getPackageName());
             return true;
         }
 
@@ -888,4 +996,17 @@
             }
         }
     }
+
+    private static class TestClock implements PackageWatchdog.SystemClock {
+        // Note 0 is special to the internal clock of PackageWatchdog. We need to start from
+        // a non-zero value in order not to disrupt the logic of PackageWatchdog.
+        private long mUpTimeMillis = 1;
+        @Override
+        public long uptimeMillis() {
+            return mUpTimeMillis;
+        }
+        public void moveTimeForward(long milliSeconds) {
+            mUpTimeMillis += milliSeconds;
+        }
+    }
 }
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bd5931..231d045b 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -31,9 +31,9 @@
 }
 
 java_test_host {
-    name: "SecondaryUserRollbackTest",
-    srcs: ["SecondaryUserRollbackTest/src/**/*.java"],
+    name: "MultiUserRollbackTest",
+    srcs: ["MultiUserRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["general-tests"],
-    test_config: "SecondaryUserRollbackTest.xml",
+    test_config: "MultiUserRollbackTest.xml",
 }
diff --git a/tests/RollbackTest/MultiUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml
new file mode 100644
index 0000000..41cec46
--- /dev/null
+++ b/tests/RollbackTest/MultiUserRollbackTest.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<configuration description="Runs rollback tests for multiple users">
+    <option name="test-suite-tag" value="MultiUserRollbackTest" />
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
+    </test>
+</configuration>
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
new file mode 100644
index 0000000..52f6eba
--- /dev/null
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.tests.rollback.host;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs rollback tests for multiple users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MultiUserRollbackTest extends BaseHostJUnit4Test {
+    // The user that was running originally when the test starts.
+    private int mOriginalUserId;
+    private int mSecondaryUserId = -1;
+    private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
+    private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
+
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().switchUser(mOriginalUserId);
+        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
+        removeSecondaryUserIfNecessary();
+    }
+
+    @Before
+    public void setup() throws Exception {
+        mOriginalUserId = getDevice().getCurrentUser();
+        installPackageAsUser("RollbackTest.apk", true, mOriginalUserId);
+        createAndSwitchToSecondaryUserIfNecessary();
+        installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId);
+    }
+
+    @Test
+    public void testBasicForSecondaryUser() throws Exception {
+        runPhaseForUsers("testBasic", mSecondaryUserId);
+    }
+
+    @Test
+    public void testMultipleUsers() throws Exception {
+        runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
+        runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId);
+        runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId);
+        switchToUser(mOriginalUserId);
+        getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
+        runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId,
+                mSecondaryUserId);
+    }
+
+    /**
+     * Run the phase for the given user ids, in the order they are given.
+     */
+    private void runPhaseForUsers(String phase, int... userIds) throws Exception {
+        for (int userId: userIds) {
+            switchToUser(userId);
+            assertTrue(runDeviceTests("com.android.tests.rollback",
+                    "com.android.tests.rollback.MultiUserRollbackTest",
+                    phase));
+        }
+    }
+
+    private void removeSecondaryUserIfNecessary() throws Exception {
+        if (mSecondaryUserId != -1) {
+            getDevice().removeUser(mSecondaryUserId);
+            mSecondaryUserId = -1;
+        }
+    }
+
+    private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
+        if (mSecondaryUserId == -1) {
+            mOriginalUserId = getDevice().getCurrentUser();
+            mSecondaryUserId = getDevice().createUser("MultiUserRollbackTest_User"
+                    + System.currentTimeMillis());
+            switchToUser(mSecondaryUserId);
+        }
+    }
+
+    private void switchToUser(int userId) throws Exception {
+        if (getDevice().getCurrentUser() == userId) {
+            return;
+        }
+
+        assertTrue(getDevice().switchUser(userId));
+        for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
+            String userState = getDevice().executeShellCommand("am get-started-user-state "
+                    + userId);
+            if (userState.contains("RUNNING_UNLOCKED")) {
+                return;
+            }
+            Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
+        }
+        fail("User switch to user " + userId + " timed out");
+    }
+}
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index 70cd867..a14b01c 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -22,8 +22,9 @@
         <option name="package" value="com.android.tests.rollback" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
 
-        <!-- Exclude the StagedRollbackTest tests, which needs to be specially
-             driven from the StagedRollbackTest host test -->
+        <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
+             specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
         <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
+        <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
     </test>
 </configuration>
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
new file mode 100644
index 0000000..0ffe041
--- /dev/null
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.tests.rollback;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+@RunWith(JUnit4.class)
+public class MultiUserRollbackTest {
+
+    @Before
+    public void adoptShellPermissions() {
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES,
+                Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                Manifest.permission.MANAGE_ROLLBACKS);
+    }
+
+    @After
+    public void dropShellPermissions() {
+        InstallUtils.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        new RollbackTest().testBasic();
+    }
+
+    /**
+     * Install version 1 of the test app. This method is run for both users.
+     */
+    @Test
+    public void testMultipleUsersInstallV1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
+    /**
+     * Upgrade the test app to version 2. This method should only run once as the system user,
+     * and will update the app for both users.
+     */
+    @Test
+    public void testMultipleUsersUpgradeToV2() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+    }
+
+    /**
+     * This method is run for both users. Assert that the test app has upgraded for both users, and
+     * update their userdata to reflect this new version.
+     */
+    @Test
+    public void testMultipleUsersUpdateUserData() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
+    /**
+     * The system will have rolled back the test app at this stage. Verify that the rollback has
+     * taken place, and that the userdata has been correctly rolled back. This method is run for
+     * both users.
+     */
+    @Test
+    public void testMultipleUsersVerifyUserdataRollback() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index bfbe08a..ed8f272 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1020,6 +1020,7 @@
 
             Uninstall.packages(TestApp.A);
             Install.single(TestApp.A1).commit();
+            waitForUnavailableRollback(TestApp.A);
 
             // Block the RollbackManager to make extra sure it will not be
             // able to enable the rollback in time.
@@ -1028,6 +1029,10 @@
 
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
 
+            // Give plenty of time for RollbackManager to unblock and attempt
+            // to make the rollback available before asserting that the
+            // rollback was not made available.
+            Thread.sleep(TimeUnit.SECONDS.toMillis(2));
             assertThat(
                 getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull();
         } finally {
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest.xml b/tests/RollbackTest/SecondaryUserRollbackTest.xml
deleted file mode 100644
index 6b3f05c..0000000
--- a/tests/RollbackTest/SecondaryUserRollbackTest.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<configuration description="Runs the rollback test from a secondary user">
-    <option name="test-suite-tag" value="SecondaryUserRollbackTest" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="RollbackTest.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
-        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class" value="com.android.tests.rollback.host.SecondaryUserRollbackTest" />
-    </test>
-</configuration>
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java b/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
deleted file mode 100644
index 11a0fbb..0000000
--- a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
+++ /dev/null
@@ -1,92 +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.tests.rollback.host;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Runs rollback tests from a secondary user.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class SecondaryUserRollbackTest extends BaseHostJUnit4Test {
-    private static final int SYSTEM_USER_ID = 0;
-    // The user that was running originally when the test starts.
-    private int mOriginalUser = SYSTEM_USER_ID;
-    private int mSecondaryUserId = -1;
-    private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
-    private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
-
-
-    @After
-    public void tearDown() throws Exception {
-        getDevice().switchUser(mOriginalUser);
-        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
-        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
-        removeSecondaryUserIfNecessary();
-    }
-
-    @Before
-    public void setup() throws Exception {
-        createAndSwitchToSecondaryUserIfNecessary();
-        installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId, "--user current");
-    }
-
-    @Test
-    public void testBasic() throws Exception {
-        assertTrue(runDeviceTests("com.android.tests.rollback",
-                "com.android.tests.rollback.RollbackTest",
-                "testBasic"));
-    }
-
-    private void removeSecondaryUserIfNecessary() throws Exception {
-        if (mSecondaryUserId != -1) {
-            getDevice().removeUser(mSecondaryUserId);
-            mSecondaryUserId = -1;
-        }
-    }
-
-    private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
-        if (mSecondaryUserId == -1) {
-            mOriginalUser = getDevice().getCurrentUser();
-            mSecondaryUserId = getDevice().createUser("SecondaryUserRollbackTest_User");
-            assertTrue(getDevice().switchUser(mSecondaryUserId));
-            // give time for user to be switched
-            waitForSwitchUserCompleted(mSecondaryUserId);
-        }
-    }
-
-    private void waitForSwitchUserCompleted(int userId) throws Exception {
-        for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
-            String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
-                    "ActivityManager:D");
-            if (logs.contains("Posting BOOT_COMPLETED user #" + userId)) {
-                return;
-            }
-            Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
-        }
-        fail("User switch to user " + userId + " timed out");
-    }
-}
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 b2e5a62..bc98f06 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
@@ -28,6 +28,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Runs the staged rollback tests.
  */
@@ -99,9 +101,15 @@
         // crash system_server enough times to trigger a rollback
         crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
 
-        // Rollback should be committed automatically now
-        // Give time for rollback to be committed
-        assertTrue(getDevice().waitForDeviceNotAvailable(60000));
+        // Rollback should be committed automatically now.
+        // Give time for rollback to be committed. This could take a while,
+        // because we need all of the following to happen:
+        // 1. system_server comes back up and boot completes.
+        // 2. Rollback health observer detects updatable crashing signal.
+        // 3. Staged rollback session becomes ready.
+        // 4. Device actually reboots.
+        // So we give a generous timeout here.
+        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
         getDevice().waitForDeviceAvailable();
 
         // verify rollback committed
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
index 7ae03e6..fefde5b 100644
--- a/tests/RollbackTest/TEST_MAPPING
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -7,7 +7,7 @@
       "name": "StagedRollbackTest"
     },
     {
-      "name": "SecondaryUserRollbackTest"
+      "name": "MultiUserRollbackTest"
     }
   ]
 }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 502aa97..e91abb6 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -20,8 +20,6 @@
         "libdl_android",
         "libhidl-gen-utils",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libjsoncpp",
         "liblog",
         "liblzma",
diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp
index 16a68d7..7d9b7b7 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -14,6 +14,39 @@
 // limitations under the License.
 //
 
+android_test {
+    name: "FrameworksNetIntegrationTests",
+    platform_apis: true,
+    certificate: "platform",
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    libs: [
+        "android.test.mock",
+    ],
+    static_libs: [
+        "TestNetworkStackLib",
+        "androidx.test.ext.junit",
+        "frameworks-net-integration-testutils",
+        "kotlin-reflect",
+        "mockito-target-extended-minus-junit4",
+        "net-tests-utils",
+        "services.core",
+        "services.net",
+        "testables",
+    ],
+    use_embedded_native_libs: true,
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+        // android_library does not include JNI libs: include NetworkStack dependencies here
+        "libnativehelper_compat_libc++",
+        "libnetworkstackutilsjni",
+    ],
+}
+
 // Utilities for testing framework code both in integration and unit tests.
 java_library {
     name: "frameworks-net-integration-testutils",
diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml
new file mode 100644
index 0000000..91b3cd9
--- /dev/null
+++ b/tests/net/integration/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.server.net.integrationtests">
+
+    <!-- For ConnectivityService registerReceiverAsUser (receiving broadcasts) -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <!-- PermissionMonitor sets network permissions for each user -->
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <!-- ConnectivityService sends notifications to BatteryStats -->
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <!-- This manifest is merged with the base manifest of the real NetworkStack app.
+             Remove the NetworkStackService from the base (real) manifest, and replace with a test
+             service that responds to the same intent -->
+        <service android:name="com.android.server.NetworkStackService" tools:node="remove"/>
+        <service android:name=".TestNetworkStackService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack">
+            <intent-filter>
+                <action android:name="android.net.INetworkStackConnector.Test"/>
+            </intent-filter>
+        </service>
+        <service android:name=".NetworkStackInstrumentationService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack">
+            <intent-filter>
+                <action android:name=".INetworkStackInstrumentation"/>
+            </intent-filter>
+        </service>
+        <service tools:replace="android:process"
+                 android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.net.integrationtests"
+                     android:label="Frameworks Net Integration Tests" />
+
+</manifest>
diff --git a/tests/net/integration/res/values/config.xml b/tests/net/integration/res/values/config.xml
new file mode 100644
index 0000000..2c8046f
--- /dev/null
+++ b/tests/net/integration/res/values/config.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--
+    Override configuration for testing. The below settings use the config_ variants, which are
+    normally used by RROs to override the setting with highest priority. -->
+    <integer name="config_captive_portal_dns_probe_timeout">12500</integer>
+    <string name="config_captive_portal_http_url" translatable="false">http://test.android.com</string>
+    <string name="config_captive_portal_https_url" translatable="false">https://secure.test.android.com</string>
+    <string-array name="config_captive_portal_fallback_urls" translatable="false">
+        <item>http://fallback1.android.com</item>
+        <item>http://fallback2.android.com</item>
+    </string-array>
+    <string-array name="config_captive_portal_fallback_probe_specs" translatable="false">
+    </string-array>
+</resources>
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
new file mode 100644
index 0000000..01eb514
--- /dev/null
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -0,0 +1,80 @@
+/*
+ * 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 android.net
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import com.android.server.net.integrationtests.TestNetworkStackService
+import org.mockito.Mockito.any
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import kotlin.test.fail
+
+const val TEST_ACTION_SUFFIX = ".Test"
+
+class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+    // TODO: consider switching to TrackRecord for more expressive checks
+    private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+
+    private class TestDependencies(private val context: Context) : Dependencies {
+        override fun addToServiceManager(service: IBinder) = Unit
+        override fun checkCallerUid() = Unit
+
+        override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
+            return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
+                getNetworkStackIntent(inSystemProcess)
+            }.also { it.init(context) }
+        }
+
+        private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
+            // Simulate out-of-system-process config: in-process service not found (null intent)
+            if (inSystemProcess) return null
+            val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
+            val serviceName = TestNetworkStackService::class.qualifiedName
+                    ?: fail("TestNetworkStackService name not found")
+            intent.component = ComponentName(context.packageName, serviceName)
+            return intent
+        }
+    }
+
+    // base may be an instance of an inaccessible subclass, so non-spyable.
+    // Use a known open class that delegates to the original instance for all methods except
+    // asBinder. asBinder needs to use its own non-delegated implementation as otherwise it would
+    // return a binder token to a class that is not spied on.
+    open class NetworkMonitorCallbacksWrapper(private val base: INetworkMonitorCallbacks) :
+            INetworkMonitorCallbacks.Stub(), INetworkMonitorCallbacks by base {
+        // asBinder is implemented by both base class and delegate: specify explicitly
+        override fun asBinder(): IBinder {
+            return super.asBinder()
+        }
+    }
+
+    override fun makeNetworkMonitor(network: Network, name: String?, cb: INetworkMonitorCallbacks) {
+        val cbSpy = spy(NetworkMonitorCallbacksWrapper(cb))
+        lastCallbacks[network] = cbSpy
+        super.makeNetworkMonitor(network, name, cbSpy)
+    }
+
+    fun verifyNetworkMonitorCreated(network: Network, timeoutMs: Long) {
+        val cb = lastCallbacks[network]
+                ?: fail("NetworkMonitor for network $network not requested")
+        verify(cb, timeout(timeoutMs)).onNetworkMonitorCreated(any())
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
new file mode 100644
index 0000000..334b26d
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Context.BIND_IMPORTANT
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.ConnectivityManager
+import android.net.IDnsResolver
+import android.net.INetd
+import android.net.INetworkPolicyManager
+import android.net.INetworkStatsService
+import android.net.LinkProperties
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkRequest
+import android.net.TestNetworkStackClient
+import android.net.metrics.IpConnectivityLog
+import android.os.ConditionVariable
+import android.os.IBinder
+import android.os.INetworkManagementService
+import android.testing.TestableContext
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.ConnectivityService
+import com.android.server.LocalServices
+import com.android.server.NetworkAgentWrapper
+import com.android.server.TestNetIdManager
+import com.android.server.connectivity.DefaultNetworkMetrics
+import com.android.server.connectivity.IpConnectivityMetrics
+import com.android.server.connectivity.MockableSystemProperties
+import com.android.server.connectivity.ProxyTracker
+import com.android.server.connectivity.Tethering
+import com.android.server.net.NetworkPolicyManagerInternal
+import com.android.testutils.TestableNetworkCallback
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.Spy
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+const val SERVICE_BIND_TIMEOUT_MS = 5_000L
+const val TEST_TIMEOUT_MS = 1_000L
+
+/**
+ * Test that exercises an instrumented version of ConnectivityService against an instrumented
+ * NetworkStack in a different test process.
+ */
+@RunWith(AndroidJUnit4::class)
+class ConnectivityServiceIntegrationTest {
+    // lateinit used here for mocks as they need to be reinitialized between each test and the test
+    // should crash if they are used before being initialized.
+    @Mock
+    private lateinit var netManager: INetworkManagementService
+    @Mock
+    private lateinit var statsService: INetworkStatsService
+    @Mock
+    private lateinit var policyManager: INetworkPolicyManager
+    @Mock
+    private lateinit var log: IpConnectivityLog
+    @Mock
+    private lateinit var netd: INetd
+    @Mock
+    private lateinit var dnsResolver: IDnsResolver
+    @Mock
+    private lateinit var metricsLogger: IpConnectivityMetrics.Logger
+    @Mock
+    private lateinit var defaultMetrics: DefaultNetworkMetrics
+    @Spy
+    private var context = TestableContext(realContext)
+
+    // lateinit for these three classes under test, as they should be reset to a different instance
+    // for every test but should always be initialized before use (or the test should crash).
+    private lateinit var networkStackClient: TestNetworkStackClient
+    private lateinit var service: ConnectivityService
+    private lateinit var cm: ConnectivityManager
+
+    companion object {
+        // lateinit for this binder token, as it must be initialized before any test code is run
+        // and use of it before init should crash the test.
+        private lateinit var nsInstrumentation: INetworkStackInstrumentation
+        private val bindingCondition = ConditionVariable(false)
+
+        private val realContext get() = InstrumentationRegistry.getInstrumentation().context
+
+        private class InstrumentationServiceConnection : ServiceConnection {
+            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+                Log.i("TestNetworkStack", "Service connected")
+                try {
+                    if (service == null) fail("Error binding to NetworkStack instrumentation")
+                    if (::nsInstrumentation.isInitialized) fail("Service already connected")
+                    nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service)
+                } finally {
+                    bindingCondition.open()
+                }
+            }
+
+            override fun onServiceDisconnected(name: ComponentName?) = Unit
+        }
+
+        @BeforeClass
+        @JvmStatic
+        fun setUpClass() {
+            val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
+            intent.action = INetworkStackInstrumentation::class.qualifiedName
+            assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(),
+                    BIND_AUTO_CREATE or BIND_IMPORTANT),
+                    "Error binding to instrumentation service")
+            assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
+                    "Timed out binding to instrumentation service " +
+                            "after $SERVICE_BIND_TIMEOUT_MS ms")
+        }
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        doReturn(defaultMetrics).`when`(metricsLogger).defaultNetworkMetrics()
+        doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any())
+
+        networkStackClient = TestNetworkStackClient(realContext)
+        networkStackClient.init()
+        networkStackClient.start()
+
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal::class.java)
+        LocalServices.addService(NetworkPolicyManagerInternal::class.java,
+                mock(NetworkPolicyManagerInternal::class.java))
+
+        service = TestConnectivityService(makeDependencies())
+        cm = ConnectivityManager(context, service)
+        context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm)
+
+        service.systemReady()
+    }
+
+    private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
+            context, netManager, statsService, policyManager, dnsResolver, log, netd, deps)
+
+    private fun makeDependencies(): ConnectivityService.Dependencies {
+        val deps = spy(ConnectivityService.Dependencies())
+        doReturn(networkStackClient).`when`(deps).networkStack
+        doReturn(metricsLogger).`when`(deps).metricsLogger
+        doReturn(mock(Tethering::class.java)).`when`(deps).makeTethering(
+                any(), any(), any(), any(), any())
+        doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
+        doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
+        doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
+        return deps
+    }
+
+    @After
+    fun tearDown() {
+        nsInstrumentation.clearAllState()
+    }
+
+    @Test
+    fun testValidation() {
+        val request = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build()
+        val testCallback = TestableNetworkCallback()
+
+        cm.registerNetworkCallback(request, testCallback)
+        nsInstrumentation.addHttpResponse(HttpResponse(
+                "http://test.android.com",
+                responseCode = 204, contentLength = 42, redirectUrl = null))
+        nsInstrumentation.addHttpResponse(HttpResponse(
+                "https://secure.test.android.com",
+                responseCode = 204, contentLength = 42, redirectUrl = null))
+
+        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
+        networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
+
+        na.addCapability(NET_CAPABILITY_INTERNET)
+        na.connect()
+
+        testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
+        assertEquals(2, nsInstrumentation.getRequestUrls().size)
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
new file mode 100644
index 0000000..9a2bcfe
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests;
+
+parcelable HttpResponse;
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
new file mode 100644
index 0000000..45073d8
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.os.Parcel
+import android.os.Parcelable
+
+data class HttpResponse(
+    val requestUrl: String,
+    val responseCode: Int,
+    val contentLength: Long,
+    val redirectUrl: String?
+) : Parcelable {
+    constructor(p: Parcel): this(p.readString(), p.readInt(), p.readLong(), p.readString())
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        with(dest) {
+            writeString(requestUrl)
+            writeInt(responseCode)
+            writeLong(contentLength)
+            writeString(redirectUrl)
+        }
+    }
+
+    override fun describeContents() = 0
+    companion object CREATOR : Parcelable.Creator<HttpResponse> {
+        override fun createFromParcel(source: Parcel) = HttpResponse(source)
+        override fun newArray(size: Int) = arrayOfNulls<HttpResponse?>(size)
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
new file mode 100644
index 0000000..efc58ad
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests;
+
+import com.android.server.net.integrationtests.HttpResponse;
+
+interface INetworkStackInstrumentation {
+    void clearAllState();
+    void addHttpResponse(in HttpResponse response);
+    List<String> getRequestUrls();
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
new file mode 100644
index 0000000..4827d29
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.app.Service
+import android.content.Intent
+import java.net.URL
+import java.util.Collections
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedQueue
+import kotlin.collections.ArrayList
+import kotlin.test.fail
+
+/**
+ * An instrumentation interface for the NetworkStack that allows controlling behavior to
+ * facilitate integration tests.
+ */
+class NetworkStackInstrumentationService : Service() {
+    override fun onBind(intent: Intent) = InstrumentationConnector.asBinder()
+
+    object InstrumentationConnector : INetworkStackInstrumentation.Stub() {
+        private val httpResponses = ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpResponse>>()
+                .run {
+                    withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } }
+                }
+        private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>())
+
+        /**
+         * Called when an HTTP request is being processed by NetworkMonitor. Returns the response
+         * that should be simulated.
+         */
+        fun processRequest(url: URL): HttpResponse {
+            val strUrl = url.toString()
+            httpRequestUrls.add(strUrl)
+            return httpResponses[strUrl]?.poll()
+                    ?: fail("No mocked response for request: $strUrl. " +
+                            "Mocked URL keys are: ${httpResponses.keys}")
+        }
+
+        /**
+         * Clear all state of this connector. This is intended for use between two tests, so all
+         * state should be reset as if the connector was just created.
+         */
+        override fun clearAllState() {
+            httpResponses.clear()
+            httpRequestUrls.clear()
+        }
+
+        /**
+         * Add a response to a future HTTP request.
+         *
+         * <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
+         * used to mock the query response.
+         */
+        override fun addHttpResponse(response: HttpResponse) {
+            httpResponses.getValue(response.requestUrl).add(response)
+        }
+
+        /**
+         * Get the ordered list of request URLs that have been sent by NetworkMonitor, and were
+         * answered based on mock responses.
+         */
+        override fun getRequestUrls(): List<String> {
+            return ArrayList(httpRequestUrls)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
new file mode 100644
index 0000000..8e4a9dd
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.INetworkMonitorCallbacks
+import android.net.Network
+import android.net.metrics.IpConnectivityLog
+import android.net.util.SharedLog
+import android.os.IBinder
+import com.android.networkstack.metrics.DataStallStatsUtils
+import com.android.server.NetworkStackService.NetworkMonitorConnector
+import com.android.server.NetworkStackService.NetworkStackConnector
+import com.android.server.connectivity.NetworkMonitor
+import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import java.net.HttpURLConnection
+import java.net.URL
+import java.net.URLConnection
+
+private const val TEST_NETID = 42
+
+/**
+ * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented
+ * through [NetworkStackInstrumentationService].
+ * Useful in tests to create test instrumented NetworkStack components that can receive
+ * instrumentation commands through [NetworkStackInstrumentationService].
+ */
+class TestNetworkStackService : Service() {
+    override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext())
+
+    private fun makeTestContext() = spy(applicationContext).also {
+        doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
+    }
+
+    private class TestPermissionChecker : NetworkStackConnector.PermissionChecker() {
+        override fun enforceNetworkStackCallingPermission() = Unit
+    }
+
+    private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
+            NetworkMonitor.Dependencies() {
+        override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+        override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
+    }
+
+    private inner class TestNetworkStackConnector(context: Context) :
+            NetworkStackConnector(context, TestPermissionChecker()) {
+
+        private val network = Network(TEST_NETID)
+        private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
+
+        private inner class TestNetwork(netId: Int) : Network(netId) {
+            override fun openConnection(url: URL): URLConnection {
+                val response = InstrumentationConnector.processRequest(url)
+
+                val connection = mock(HttpURLConnection::class.java)
+                doReturn(response.responseCode).`when`(connection).responseCode
+                doReturn(response.contentLength).`when`(connection).contentLengthLong
+                doReturn(response.redirectUrl).`when`(connection).getHeaderField("location")
+                return connection
+            }
+        }
+
+        override fun makeNetworkMonitor(
+            network: Network,
+            name: String?,
+            cb: INetworkMonitorCallbacks
+        ) {
+            val nm = NetworkMonitor(this@TestNetworkStackService, cb,
+                    this.network,
+                    mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+                    NetworkMonitorDeps(privateDnsBypassNetwork),
+                    mock(DataStallStatsUtils::class.java))
+            cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker()))
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f3c735c..9e21db7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -184,6 +184,7 @@
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -503,6 +504,8 @@
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
             waitForIdle(TIMEOUT_MS);
+            HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+            HandlerUtilsKt.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
         }
 
         @Override
@@ -2259,9 +2262,8 @@
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
@@ -2281,9 +2283,8 @@
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
@@ -2306,9 +2307,8 @@
         // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
         // notifyNetworkConnected.
         mWiFiNetworkAgent.connectWithPartialValidConnectivity();
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(
                 NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -3633,6 +3633,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 140305589)
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -4316,16 +4317,16 @@
         assertFalse(mCm.isNetworkSupported(TYPE_NONE));
 
         assertThrows(IllegalArgumentException.class,
-                () -> { mCm.networkCapabilitiesForType(TYPE_NONE); });
+                () -> mCm.networkCapabilitiesForType(TYPE_NONE));
 
         Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class;
-        assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_WIFI, ""); });
-        assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_WIFI, ""); });
+        assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_WIFI, ""));
+        assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_WIFI, ""));
         // TODO: let test context have configuration application target sdk version
         // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED
-        assertThrows(unsupported, () -> { mCm.startUsingNetworkFeature(TYPE_NONE, ""); });
-        assertThrows(unsupported, () -> { mCm.stopUsingNetworkFeature(TYPE_NONE, ""); });
-        assertThrows(unsupported, () -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); });
+        assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_NONE, ""));
+        assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_NONE, ""));
+        assertThrows(unsupported, () -> mCm.requestRouteToHostAddress(TYPE_NONE, null));
     }
 
     @Test
@@ -5692,26 +5693,40 @@
         String[] values = tcpBufferSizes.split(",");
         String rmemValues = String.join(" ", values[0], values[1], values[2]);
         String wmemValues = String.join(" ", values[3], values[4], values[5]);
-        waitForIdle();
         verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues);
         reset(mMockNetd);
     }
 
     @Test
+    @FlakyTest(bugId = 140305678)
     public void testTcpBufferReset() throws Exception {
         final String testTcpBufferSizes = "1,2,3,4,5,6";
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(networkRequest, networkCallback);
 
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         reset(mMockNetd);
         // Switching default network updates TCP buffer sizes.
         mCellNetworkAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
 
         // Change link Properties should have updated tcp buffer size.
         LinkProperties lp = new LinkProperties();
         lp.setTcpBufferSizes(testTcpBufferSizes);
         mCellNetworkAgent.sendLinkProperties(lp);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verifyTcpBufferSizeChange(testTcpBufferSizes);
+
+        // Clean up.
+        mCellNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        networkCallback.assertNoCallback();
+        mCm.unregisterNetworkCallback(networkCallback);
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/NetIdManagerTest.kt b/tests/net/java/com/android/server/NetIdManagerTest.kt
new file mode 100644
index 0000000..045f89f
--- /dev/null
+++ b/tests/net/java/com/android/server/NetIdManagerTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.server.NetIdManager.MIN_NET_ID
+import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetIdManagerTest {
+    @Test
+    fun testReserveReleaseNetId() {
+        val manager = NetIdManager(MIN_NET_ID + 4)
+        assertEquals(MIN_NET_ID, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
+
+        manager.releaseNetId(MIN_NET_ID + 1)
+        manager.releaseNetId(MIN_NET_ID + 3)
+        // IDs only loop once there is no higher ID available
+        assertEquals(MIN_NET_ID + 4, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
+        assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
+        manager.releaseNetId(MIN_NET_ID + 5)
+        // Still no ID available: MIN_NET_ID + 5 was not reserved
+        assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
+        manager.releaseNetId(MIN_NET_ID + 2)
+        // Throwing an exception still leaves the manager in a working state
+        assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
+    }
+}
\ No newline at end of file
diff --git a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
index 273943f..4172743 100644
--- a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
@@ -16,12 +16,12 @@
 
 package com.android.framework.permission.tests;
 
-import java.util.ArrayList;
-
 import android.telephony.SmsManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+
 /**
  * Verify that SmsManager apis cannot be called without required permissions.
  */
@@ -32,6 +32,10 @@
     private static final String DEST_NUMBER = "4567";
     private static final String SRC_NUMBER = "1234";
 
+    private static final int CELL_BROADCAST_MESSAGE_ID_START = 10;
+    private static final int CELL_BROADCAST_MESSAGE_ID_END = 20;
+    private static final int CELL_BROADCAST_GSM_RAN_TYPE = 0;
+
     /**
      * Verify that SmsManager.sendTextMessage requires permissions.
      * <p>Tests Permission:
@@ -82,4 +86,34 @@
             // expected
         }
     }
+
+    /**
+     * Verify that SmsManager.enableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testEnableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().enableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that SmsManager.disableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testDisableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().disableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
 }
diff --git a/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java b/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
index 1b8e58c..7057a90 100644
--- a/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
+++ b/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import libcore.util.SneakyThrow;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -55,7 +57,11 @@
      * WARNING: This is absolutely incompatible with running tests in parallel!
      */
     public static void runWithDexmakerShareClassLoader(Runnable r) {
-        apply(r::run).run();
+        try {
+            apply(r::run).run();
+        } catch (Throwable t) {
+            SneakyThrow.sneakyThrow(t);
+        }
     }
 
     /**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index a49eda3..01bd47b 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -210,33 +210,36 @@
         /**
          * Run method for the auto dispatch thread.
          * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
-         * The thread continues looping and attempting to dispatch all messages until at
-         * least one message has been dispatched.
+         * The thread continues looping and attempting to dispatch all messages until
+         * {@link #stopAutoDispatch()} has been invoked.
          */
         @Override
         public void run() {
             int dispatchCount = 0;
             for (int i = 0; i < MAX_LOOPS; i++) {
                 try {
-                    dispatchCount = dispatchAll();
+                    dispatchCount += dispatchAll();
                 } catch (RuntimeException e) {
                     mAutoDispatchException = e;
-                }
-                Log.d(TAG, "dispatched " + dispatchCount + " messages");
-                if (dispatchCount > 0) {
                     return;
                 }
+                Log.d(TAG, "dispatched " + dispatchCount + " messages");
                 try {
                     Thread.sleep(LOOP_SLEEP_TIME_MS);
                 } catch (InterruptedException e) {
-                    mAutoDispatchException = new IllegalStateException(
-                            "stopAutoDispatch called before any messages were dispatched.");
+                    if (dispatchCount == 0) {
+                        Log.e(TAG, "stopAutoDispatch called before any messages were dispatched.");
+                        mAutoDispatchException = new IllegalStateException(
+                                "stopAutoDispatch called before any messages were dispatched.");
+                    }
                     return;
                 }
             }
-            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
-            mAutoDispatchException = new IllegalStateException(
-                    "TestLooper did not dispatch any messages before exiting.");
+            if (dispatchCount == 0) {
+                Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
+                mAutoDispatchException = new IllegalStateException(
+                        "TestLooper did not dispatch any messages before exiting.");
+            }
         }
 
         /**
@@ -287,4 +290,17 @@
                     "stopAutoDispatch called without startAutoDispatch.");
         }
     }
+
+    /**
+     * If an AutoDispatchThread is currently running, stop and clean up.
+     * This method ignores exceptions raised for indicating that no messages were dispatched.
+     */
+    public void stopAutoDispatchAndIgnoreExceptions() {
+        try {
+            stopAutoDispatch();
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "stopAutoDispatch", e);
+        }
+
+    }
 }
diff --git a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
index c83dfa4..e05fdce 100644
--- a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
+++ b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
@@ -31,7 +31,7 @@
     }
 
     @Override
-    public void close() throws Exception {
+    public void close() {
         ViewRootImpl.sNewInsetsMode = mOldMode;
     }
 }
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 7ffa5ff..137fbd6 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -246,6 +246,36 @@
   Printer* printer_;
 };
 
+std::string OverlayablePoliciesToString(OverlayableItem::PolicyFlags policies) {
+  static const std::map<OverlayableItem::PolicyFlags, std::string> kFlagToString = {
+    {OverlayableItem::kPublic, "public"},
+    {OverlayableItem::kSystem, "system"},
+    {OverlayableItem::kVendor, "vendor"},
+    {OverlayableItem::kProduct, "product"},
+    {OverlayableItem::kSignature, "signature"},
+    {OverlayableItem::kOdm, "odm"},
+    {OverlayableItem::kOem, "oem"},
+  };
+  std::string str;
+  for (auto const& policy : kFlagToString) {
+    if ((policies & policy.first) != policy.first) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.second);
+    policies &= ~policy.first;
+  }
+  if (policies != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", policies));
+  }
+  return !str.empty() ? str : "none";
+}
+
 }  // namespace
 
 void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -312,6 +342,10 @@
             break;
         }
 
+        if (entry->overlayable_item) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
         if (options.show_values) {
@@ -525,4 +559,62 @@
   doc.root->Accept(&xml_visitor);
 }
 
+struct DumpOverlayableEntry {
+  std::string overlayable_section;
+  std::string policy_subsection;
+  std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+  std::vector<DumpOverlayableEntry> items;
+  for (const auto& package : table.packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        if (entry->overlayable_item) {
+          const auto& overlayable_item = entry->overlayable_item.value();
+          const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+              overlayable_item.overlayable->name.c_str(),
+              overlayable_item.overlayable->actor.c_str());
+          const auto policy_subsection = StringPrintf(R"(policies="%s")",
+              OverlayablePoliciesToString(overlayable_item.policies).c_str());
+          const auto value =
+            StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+          items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+        }
+      }
+    }
+  }
+
+  std::sort(items.begin(), items.end(),
+      [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+        if (a.overlayable_section != b.overlayable_section) {
+          return a.overlayable_section < b.overlayable_section;
+        }
+        if (a.policy_subsection != b.policy_subsection) {
+          return a.policy_subsection < b.policy_subsection;
+        }
+        return a.resource_name < b.resource_name;
+      });
+
+  std::string last_overlayable_section;
+  std::string last_policy_subsection;
+  for (const auto& item : items) {
+    if (last_overlayable_section != item.overlayable_section) {
+      printer->Println(item.overlayable_section);
+      last_overlayable_section = item.overlayable_section;
+    }
+    if (last_policy_subsection != item.policy_subsection) {
+      printer->Indent();
+      printer->Println(item.policy_subsection);
+      last_policy_subsection = item.policy_subsection;
+      printer->Undent();
+    }
+    printer->Indent();
+    printer->Indent();
+    printer->Println(item.resource_name);
+    printer->Undent();
+    printer->Undent();
+  }
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197c..9443d60 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
   static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+  static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 213bdd2..7966ba2 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -177,6 +177,11 @@
   return main_command->Execute(args, &std::cerr);
 }
 
+// TODO(b/141312058) stop leaks
+extern "C" const char *__asan_default_options() {
+    return "detect_leaks=0";
+}
+
 int main(int argc, char** argv) {
 #ifdef _WIN32
   LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index c7ac438..d50b1de 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -740,7 +740,6 @@
   }
 
   std::unique_ptr<io::IFileCollection> file_collection;
-  std::unique_ptr<IArchiveWriter> archive_writer;
 
   // Collect the resources files to compile
   if (options_.res_dir && options_.res_zip) {
@@ -761,8 +760,6 @@
       context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
       return 1;
     }
-
-    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   } else if (options_.res_zip) {
     if (!args.empty()) {
       context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
@@ -777,8 +774,6 @@
       context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
       return 1;
     }
-
-    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   } else {
     auto collection = util::make_unique<io::FileCollection>();
 
@@ -791,7 +786,14 @@
     }
 
     file_collection = std::move(collection);
+  }
+
+  std::unique_ptr<IArchiveWriter> archive_writer;
+  file::FileType output_file_type = file::GetFileType(options_.output_path);
+  if (output_file_type == file::FileType::kDirectory) {
     archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
+  } else {
+    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   }
 
   if (!archive_writer) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 429aff1..3982d12 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -394,6 +394,17 @@
   return 0;
 }
 
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+  ResourceTable* table = apk->GetResourceTable();
+  if (!table) {
+    GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+    return 1;
+  }
+
+  Debug::DumpOverlayable(*table, GetPrinter());
+  return 0;
+}
+
 const char DumpBadgerCommand::kBadgerData[2925] = {
     32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
     32,  32,  32,  32,  32,  32,  95,  46,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bc..cd51f7a 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@
   std::vector<std::string> files_;
 };
 
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+  explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+      : DumpApkCommand("overlayable", printer, diag) {
+    SetDescription("Print the <overlayable> resources of an APK.");
+  }
+
+  int Dump(LoadedApk* apk) override;
+};
+
 /** The default dump command. Performs no action because a subcommand is required. */
 class DumpCommand : public Command {
  public:
@@ -255,8 +265,8 @@
     AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+    AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
-    // TODO(b/120609160): Add aapt2 overlayable dump command
   }
 
   int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 5e06818..e36668e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -53,9 +53,9 @@
 using ::android::ResTable_config;
 using ::android::StringPiece;
 using ::android::base::ReadFileToString;
-using ::android::base::WriteStringToFile;
 using ::android::base::StringAppendF;
 using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFile;
 
 namespace aapt {
 
@@ -300,29 +300,7 @@
   OptimizeContext* context_;
 };
 
-bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context,
-                                           OptimizeOptions* options) {
-  std::string contents;
-  if (!ReadFileToString(path, &contents, true)) {
-    context->GetDiagnostics()->Error(DiagMessage()
-                                     << "failed to parse whitelist from config file: " << path);
-    return false;
-  }
-  for (StringPiece resource_name : util::Tokenize(contents, ',')) {
-    options->table_flattener_options.whitelisted_resources.insert(
-        resource_name.to_string());
-  }
-  return true;
-}
-
-bool ExtractConfig(const std::string& path, OptimizeContext* context,
-                                    OptimizeOptions* options) {
-  std::string content;
-  if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
-    context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist");
-    return false;
-  }
-
+bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) {
   size_t line_no = 0;
   for (StringPiece line : util::Tokenize(content, '\n')) {
     line_no++;
@@ -351,15 +329,24 @@
     for (StringPiece directive : util::Tokenize(directives, ',')) {
       if (directive == "remove") {
         options->resources_blacklist.insert(resource_name.ToResourceName());
-      } else if (directive == "no_obfuscate") {
-        options->table_flattener_options.whitelisted_resources.insert(
-            resource_name.entry.to_string());
+      } else if (directive == "no_collapse" || directive == "no_obfuscate") {
+        options->table_flattener_options.name_collapse_exemptions.insert(
+            resource_name.ToResourceName());
       }
     }
   }
   return true;
 }
 
+bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) {
+  std::string content;
+  if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+    context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file");
+    return false;
+  }
+  return ParseConfig(content, context, options);
+}
+
 bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
                                 OptimizeOptions* out_options) {
   const xml::XmlResource* manifest = apk->GetManifest();
@@ -467,15 +454,6 @@
     }
   }
 
-  if (options_.table_flattener_options.collapse_key_stringpool) {
-    if (whitelist_path_) {
-      std::string& path = whitelist_path_.value();
-      if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) {
-        return 1;
-      }
-    }
-  }
-
   if (resources_config_path_) {
     std::string& path = resources_config_path_.value();
     if (!ExtractConfig(path, &context, &options_)) {
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 0be7dad..5070ccc 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -78,10 +78,6 @@
             "All the resources that would be unused on devices of the given densities will be \n"
             "removed from the APK.",
         &target_densities_);
-    AddOptionalFlag("--whitelist-path",
-        "Path to the whitelist.cfg file containing whitelisted resources \n"
-            "whose names should not be altered in final resource tables.",
-        &whitelist_path_);
     AddOptionalFlag("--resources-config-path",
         "Path to the resources.cfg file containing the list of resources and \n"
             "directives to each resource. \n"
@@ -104,11 +100,13 @@
         "Enables encoding sparse entries using a binary search tree.\n"
             "This decreases APK size at the cost of resource retrieval performance.",
         &options_.table_flattener_options.use_sparse_entries);
-    AddOptionalSwitch("--enable-resource-obfuscation",
-        "Enables obfuscation of key string pool to single value",
+    AddOptionalSwitch("--collapse-resource-names",
+        "Collapses resource names to a single value in the key string pool. Resources can \n"
+            "be exempted using the \"no_collapse\" directive in a file specified by "
+            "--resources-config-path.",
         &options_.table_flattener_options.collapse_key_stringpool);
-    AddOptionalSwitch("--enable-resource-path-shortening",
-        "Enables shortening of the path of the resources inside the APK.",
+    AddOptionalSwitch("--shorten-resource-paths",
+        "Shortens the paths of resources inside the APK.",
         &options_.shorten_resource_paths);
     AddOptionalFlag("--resource-path-shortening-map",
         "Path to output the map of old resource paths to shortened paths.",
@@ -125,7 +123,6 @@
                                const std::string &file_path);
 
   Maybe<std::string> config_path_;
-  Maybe<std::string> whitelist_path_;
   Maybe<std::string> resources_config_path_;
   Maybe<std::string> target_densities_;
   std::vector<std::string> configs_;
diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp
new file mode 100644
index 0000000..ac681e8
--- /dev/null
+++ b/tools/aapt2/cmd/Optimize_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#include "Optimize.h"
+
+#include "AppInfo.h"
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Resource.h"
+#include "test/Test.h"
+
+using testing::Contains;
+using testing::Eq;
+
+namespace aapt {
+
+bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*);
+
+using OptimizeTest = CommandTestFixture;
+
+TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) {
+  const std::string& content = R"(
+string/foo#no_collapse
+dimen/bar#no_collapse
+)";
+  aapt::test::Context context;
+  OptimizeOptions options;
+  ParseConfig(content, &context, &options);
+
+  const std::set<ResourceName>& name_collapse_exemptions =
+      options.table_flattener_options.name_collapse_exemptions;
+
+  ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) {
+  const std::string& content = R"(
+string/foo#no_obfuscate
+dimen/bar#no_obfuscate
+)";
+  aapt::test::Context context;
+  OptimizeOptions options;
+  ParseConfig(content, &context, &options);
+
+  const std::set<ResourceName>& name_collapse_exemptions =
+      options.table_flattener_options.name_collapse_exemptions;
+
+  ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index b932117..58e232c 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -228,14 +228,15 @@
  public:
   PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
                    const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
-                   bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
+                   bool collapse_key_stringpool,
+                   const std::set<ResourceName>& name_collapse_exemptions)
       : context_(context),
         diag_(context->GetDiagnostics()),
         package_(package),
         shared_libs_(shared_libs),
         use_sparse_entries_(use_sparse_entries),
         collapse_key_stringpool_(collapse_key_stringpool),
-        whitelisted_resources_(whitelisted_resources) {
+        name_collapse_exemptions_(name_collapse_exemptions) {
   }
 
   bool FlattenPackage(BigBuffer* buffer) {
@@ -652,11 +653,12 @@
 
       for (ResourceEntry* entry : sorted_entries) {
         uint32_t local_key_index;
+        ResourceName resource_name({}, type->type, entry->name);
         if (!collapse_key_stringpool_ ||
-            whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
+            name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
           local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
         } else {
-          // resource isn't whitelisted, add it as obfuscated value
+          // resource isn't exempt from collapse, add it as obfuscated value
           local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
         }
         // Group values by configuration.
@@ -712,7 +714,7 @@
   StringPool type_pool_;
   StringPool key_pool_;
   bool collapse_key_stringpool_;
-  const std::set<std::string>& whitelisted_resources_;
+  const std::set<ResourceName>& name_collapse_exemptions_;
 };
 
 }  // namespace
@@ -760,7 +762,7 @@
 
     PackageFlattener flattener(context, package.get(), &table->included_packages_,
                                options_.use_sparse_entries, options_.collapse_key_stringpool,
-                               options_.whitelisted_resources);
+                               options_.name_collapse_exemptions);
     if (!flattener.FlattenPackage(&package_buffer)) {
       return false;
     }
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 73c1729..4360db1 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -19,6 +19,7 @@
 
 #include "android-base/macros.h"
 
+#include "Resource.h"
 #include "ResourceTable.h"
 #include "process/IResourceTableConsumer.h"
 #include "util/BigBuffer.h"
@@ -41,8 +42,8 @@
   // have name indices that point to this single value
   bool collapse_key_stringpool = false;
 
-  // Set of whitelisted resource names to avoid altering in key stringpool
-  std::set<std::string> whitelisted_resources;
+  // Set of resources to avoid collapsing to a single entry in key stringpool.
+  std::set<ResourceName> name_collapse_exemptions;
 
   // Map from original resource paths to shortened resource paths.
   std::map<std::string, std::string> shortened_path_map;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index a940923..8fbdd7f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -518,7 +518,7 @@
   ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
 }
 
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
@@ -572,7 +572,7 @@
                      ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
 }
 
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
@@ -591,21 +591,22 @@
 
   TableFlattenerOptions options;
   options.collapse_key_stringpool = true;
-  options.whitelisted_resources.insert("test");
-  options.whitelisted_resources.insert("three");
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
   ResTable res_table;
 
   ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
 
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
                      ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
   EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
                      ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
-                     Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+                     ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
 
+  // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
   EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
                      ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
                      ResTable_config::CONFIG_VERSION));
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index f3be483..c3c16b9 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -83,6 +83,15 @@
     Attribute default_attribute(android::ResTable_map::TYPE_ANY);
     default_attribute.SetWeak(true);
 
+    // The default orientation of gradients in android Q is different than previous android
+    // versions. Set the android:angle attribute to "0" to ensure that the default gradient
+    // orientation will remain left-to-right in android Q.
+    if (el->name == "gradient" && context_->GetMinSdkVersion() <= SDK_Q) {
+      if (!el->FindAttribute(xml::kSchemaAndroid, "angle")) {
+        el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "angle", "0"});
+      }
+    }
+
     const Source source = source_.WithLine(el->line_number);
     for (xml::Attribute& attr : el->attributes) {
       // If the attribute has no namespace, interpret values as if
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index ef99355..0ce2e50 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -47,6 +47,8 @@
                                             test::AttributeBuilder()
                                                 .SetTypeMask(android::ResTable_map::TYPE_STRING)
                                                 .Build())
+                           .AddPublicSymbol("android:attr/angle", ResourceId(0x01010004),
+                                            test::AttributeBuilder().Build())
 
                            // Add one real symbol that was introduces in v21
                            .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
@@ -75,7 +77,7 @@
   }
 
  protected:
-  std::unique_ptr<IAaptContext> context_;
+  std::unique_ptr<test::Context> context_;
 };
 
 TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
@@ -254,4 +256,63 @@
   EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id);
 }
 
+
+TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+    <gradient />)");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, NotNull());
+  ASSERT_TRUE(xml_attr->compiled_attribute);
+  EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+  BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+  EXPECT_EQ(value->value.data, 0U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+  <gradient xmlns:android="http://schemas.android.com/apk/res/android"
+      android:angle="90"/>)");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, NotNull());
+  ASSERT_TRUE(xml_attr->compiled_attribute);
+  EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+  BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+  EXPECT_EQ(value->value.data, 90U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+  <gradient xmlns:android="http://schemas.android.com/apk/res/android" />)");
+  context_->SetMinSdkVersion(30);
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, IsNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 7e10a59..553c43e 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -81,6 +81,10 @@
     return min_sdk_version_;
   }
 
+  void SetMinSdkVersion(int min_sdk_version) {
+    min_sdk_version_ = min_sdk_version;
+  }
+
  const std::set<std::string>& GetSplitNameDependencies() override {
     return split_name_dependencies_;
   }
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 0f932f3..fa2b41a 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -107,7 +107,7 @@
         println(CODEGEN_VERSION)
         System.exit(0)
     }
-    val file = File(args.last())
+    val file = File(args.last()).absoluteFile
     val sourceLinesNoClosingBrace = file.readLines().dropLastWhile {
         it.startsWith("}") || it.all(Char::isWhitespace)
     }
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 7d50ad1..b2cc813 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
 package com.android.codegen
 
 const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.0"
+const val CODEGEN_VERSION = "1.0.1"
 
 const val CANONICAL_BUILDER_CLASS = "Builder"
 const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
new file mode 100644
index 0000000..d1a86c2
--- /dev/null
+++ b/tools/protologtool/Android.bp
@@ -0,0 +1,33 @@
+java_library_host {
+    name: "protologtool-lib",
+    srcs: [
+        "src/com/android/protolog/tool/**/*.kt",
+    ],
+    static_libs: [
+        "protolog-common",
+        "javaparser",
+        "protolog-proto",
+        "jsonlib",
+    ],
+}
+
+java_binary_host {
+    name: "protologtool",
+    manifest: "manifest.txt",
+    static_libs: [
+        "protologtool-lib",
+    ],
+}
+
+java_test_host {
+    name: "protologtool-tests",
+    test_suites: ["general-tests"],
+    srcs: [
+        "tests/**/*.kt",
+    ],
+    static_libs: [
+        "protologtool-lib",
+        "junit",
+        "mockito",
+    ],
+}
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
new file mode 100644
index 0000000..3439357
--- /dev/null
+++ b/tools/protologtool/README.md
@@ -0,0 +1,106 @@
+# ProtoLogTool
+
+Code transformation tool and viewer for ProtoLog.
+
+## What does it do?
+
+ProtoLogTool incorporates three different modes of operation:
+
+### Code transformation
+
+Command: `process <protolog class path> <protolog implementation class path>
+ <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
+
+In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
+```java
+ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
+```
+into:
+```java
+if (GROUP_NAME.isLogToAny()) {
+    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
+}
+```
+where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
+ (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
+ logging method. The transformation is done on the source level. A hash is generated from the format
+ string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced
+ by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
+ returns false the log statement is removed entirely from the resultant code.
+
+Input is provided as a list of java source file names. Transformed source is saved to a single
+source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
+jar file (config.jar).
+
+### Viewer config generation
+
+Command: `viewerconf <protolog class path> <protolog implementation class path
+<protolog groups class path> <config.jar> [<input.java>] <output.json>`
+
+This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
+it writes a viewer configuration file with following schema:
+```json
+{
+  "version": "1.0.0",
+  "messages": {
+    "123456": {
+      "message": "Format string %d %s",
+      "level": "ERROR",
+      "group": "GROUP_NAME"
+    },
+  },
+  "groups": {
+    "GROUP_NAME": {
+      "tag": "TestLog"
+    }
+  }
+}
+
+```
+
+### Binary log viewing
+
+Command: `read <viewer.json> <wm_log.pb>`
+
+Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
+
+## What is ProtoLog?
+
+ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging
+and is tunable in runtime. It consists of 3 different submodules:
+* logging system built-in the Android app,
+* log viewer for reading binary logs,
+* a code processing tool.
+
+ProtoLog is designed to reduce both application size (and by that memory usage) and amount of resources needed
+for logging. This is achieved by replacing log message strings with their hashes and only loading to memory/writing
+full log messages when necessary.
+
+### Text logging
+
+For text-based logs Android LogCat is used as a backend. Message strings are loaded from a viewer config
+located on the device when needed.
+
+### Binary logging
+
+Binary logs are saved as Protocol Buffers file. They can be read using the ProtoLog tool or specialised
+viewer like Winscope.
+
+## How to use ProtoLog?
+
+### Adding a new logging group or log statement
+
+To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with desired parameters.
+
+To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
+
+After doing any changes to logging groups or statements you should run `make update-protolog` to update
+viewer configuration saved in the code repository.
+
+## How to change settings on device in runtime?
+Use the `adb shell su root cmd window logging` command. To get help just type
+`adb shell su root cmd window logging help`.
+
+
+
+
diff --git a/tools/protologtool/TEST_MAPPING b/tools/protologtool/TEST_MAPPING
new file mode 100644
index 0000000..52b12dc
--- /dev/null
+++ b/tools/protologtool/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "protologtool-tests"
+    }
+  ]
+}
diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt
new file mode 100644
index 0000000..cabebd5
--- /dev/null
+++ b/tools/protologtool/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.protolog.tool.ProtoLogTool
diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
new file mode 100644
index 0000000..5c92161
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.ImportDeclaration
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.StringLiteralExpr
+
+object CodeUtils {
+    /**
+     * Returns a stable hash of a string.
+     * We reimplement String::hashCode() for readability reasons.
+     */
+    fun hash(str: String, level: LogLevel): Int {
+        return (level.name + str).map { c -> c.toInt() }.reduce { h, c -> h * 31 + c }
+    }
+
+    fun isWildcardStaticImported(code: CompilationUnit, className: String): Boolean {
+        return code.findAll(ImportDeclaration::class.java)
+                .any { im -> im.isStatic && im.isAsterisk && im.name.toString() == className }
+    }
+
+    fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
+        val packageName = className.substringBeforeLast('.')
+        return code.packageDeclaration.isPresent &&
+                code.packageDeclaration.get().nameAsString == packageName ||
+                code.findAll(ImportDeclaration::class.java)
+                        .any { im ->
+                            !im.isStatic &&
+                                    ((!im.isAsterisk && im.name.toString() == className) ||
+                                            (im.isAsterisk && im.name.toString() == packageName))
+                        }
+    }
+
+    fun staticallyImportedMethods(code: CompilationUnit, className: String): Set<String> {
+        return code.findAll(ImportDeclaration::class.java)
+                .filter { im ->
+                    im.isStatic &&
+                            im.name.toString().substringBeforeLast('.') == className
+                }
+                .map { im -> im.name.toString().substringAfterLast('.') }.toSet()
+    }
+
+    fun concatMultilineString(expr: Expression): String {
+        return when (expr) {
+            is StringLiteralExpr -> expr.asString()
+            is BinaryExpr -> when {
+                expr.operator == BinaryExpr.Operator.PLUS ->
+                    concatMultilineString(expr.left) + concatMultilineString(expr.right)
+                else -> throw InvalidProtoLogCallException(
+                        "messageString must be a string literal " +
+                                "or concatenation of string literals.", expr)
+            }
+            else -> throw InvalidProtoLogCallException("messageString must be a string literal " +
+                    "or concatenation of string literals.", expr)
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
new file mode 100644
index 0000000..3dfa4d2
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.protolog.tool
+
+import java.util.regex.Pattern
+
+class CommandOptions(args: Array<String>) {
+    companion object {
+        const val TRANSFORM_CALLS_CMD = "transform-protolog-calls"
+        const val GENERATE_CONFIG_CMD = "generate-viewer-config"
+        const val READ_LOG_CMD = "read-log"
+        private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+
+        private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
+        private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
+        private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
+        private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
+        private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
+        private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
+        private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
+                PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_JSON_PARAM,
+                OUTPUT_SOURCE_JAR_PARAM)
+
+        val USAGE = """
+            Usage: ${Constants.NAME} <command> [<args>]
+            Available commands:
+
+            $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
+                <class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
+                <config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
+            - processes java files replacing stub calls with logging code.
+
+            $GENERATE_CONFIG_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGGROUP_CLASS_PARAM
+                <class name> $PROTOLOGGROUP_JAR_PARAM <config.jar> $VIEWER_CONFIG_JSON_PARAM
+                <viewer.json> [<input.java>]
+            - creates viewer config file from given java files.
+
+            $READ_LOG_CMD $VIEWER_CONFIG_JSON_PARAM <viewer.json> <wm_log.pb>
+            - translates a binary log to a readable format.
+        """.trimIndent()
+
+        private fun validateClassName(name: String): String {
+            if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9]+)$", name)) {
+                throw InvalidCommandException("Invalid class name $name")
+            }
+            return name
+        }
+
+        private fun getParam(paramName: String, params: Map<String, String>): String {
+            if (!params.containsKey(paramName)) {
+                throw InvalidCommandException("Param $paramName required")
+            }
+            return params.getValue(paramName)
+        }
+
+        private fun validateNotSpecified(paramName: String, params: Map<String, String>): String {
+            if (params.containsKey(paramName)) {
+                throw InvalidCommandException("Unsupported param $paramName")
+            }
+            return ""
+        }
+
+        private fun validateJarName(name: String): String {
+            if (!name.endsWith(".jar")) {
+                throw InvalidCommandException("Jar file required, got $name instead")
+            }
+            return name
+        }
+
+        private fun validateSrcJarName(name: String): String {
+            if (!name.endsWith(".srcjar")) {
+                throw InvalidCommandException("Source jar file required, got $name instead")
+            }
+            return name
+        }
+
+        private fun validateJSONName(name: String): String {
+            if (!name.endsWith(".json")) {
+                throw InvalidCommandException("Json file required, got $name instead")
+            }
+            return name
+        }
+
+        private fun validateJavaInputList(list: List<String>): List<String> {
+            if (list.isEmpty()) {
+                throw InvalidCommandException("No java source input files")
+            }
+            list.forEach { name ->
+                if (!name.endsWith(".java")) {
+                    throw InvalidCommandException("Not a java source file $name")
+                }
+            }
+            return list
+        }
+
+        private fun validateLogInputList(list: List<String>): String {
+            if (list.isEmpty()) {
+                throw InvalidCommandException("No log input file")
+            }
+            if (list.size > 1) {
+                throw InvalidCommandException("Only one log input file allowed")
+            }
+            return list[0]
+        }
+    }
+
+    val protoLogClassNameArg: String
+    val protoLogGroupsClassNameArg: String
+    val protoLogImplClassNameArg: String
+    val protoLogGroupsJarArg: String
+    val viewerConfigJsonArg: String
+    val outputSourceJarArg: String
+    val logProtofileArg: String
+    val javaSourceArgs: List<String>
+    val command: String
+
+    init {
+        if (args.isEmpty()) {
+            throw InvalidCommandException("No command specified.")
+        }
+        command = args[0]
+        if (command !in commands) {
+            throw InvalidCommandException("Unknown command.")
+        }
+
+        val params: MutableMap<String, String> = mutableMapOf()
+        val inputFiles: MutableList<String> = mutableListOf()
+
+        var idx = 1
+        while (idx < args.size) {
+            if (args[idx].startsWith("--")) {
+                if (idx + 1 >= args.size) {
+                    throw InvalidCommandException("No value for ${args[idx]}")
+                }
+                if (args[idx] !in parameters) {
+                    throw InvalidCommandException("Unknown parameter ${args[idx]}")
+                }
+                if (args[idx + 1].startsWith("--")) {
+                    throw InvalidCommandException("No value for ${args[idx]}")
+                }
+                if (params.containsKey(args[idx])) {
+                    throw InvalidCommandException("Duplicated parameter ${args[idx]}")
+                }
+                params[args[idx]] = args[idx + 1]
+                idx += 2
+            } else {
+                inputFiles.add(args[idx])
+                idx += 1
+            }
+        }
+
+        when (command) {
+            TRANSFORM_CALLS_CMD -> {
+                protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+                protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+                        params))
+                protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
+                        params))
+                protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+                viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
+                outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
+                javaSourceArgs = validateJavaInputList(inputFiles)
+                logProtofileArg = ""
+            }
+            GENERATE_CONFIG_CMD -> {
+                protoLogClassNameArg = validateClassName(getParam(PROTOLOG_CLASS_PARAM, params))
+                protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
+                        params))
+                protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
+                viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+                outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+                javaSourceArgs = validateJavaInputList(inputFiles)
+                logProtofileArg = ""
+            }
+            READ_LOG_CMD -> {
+                protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
+                protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
+                protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
+                viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
+                outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
+                javaSourceArgs = listOf()
+                logProtofileArg = validateLogInputList(inputFiles)
+            }
+            else -> {
+                throw InvalidCommandException("Unknown command.")
+            }
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/Constants.kt b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
new file mode 100644
index 0000000..83b3c00
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/Constants.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.protolog.tool
+
+object Constants {
+        const val NAME = "protologtool"
+        const val VERSION = "1.0.0"
+        const val IS_LOG_TO_ANY_METHOD = "isLogToAny"
+        const val ENUM_VALUES_METHOD = "values"
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt
new file mode 100644
index 0000000..587f7b9
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.protolog.tool
+
+data class LogGroup(
+    val name: String,
+    val enabled: Boolean,
+    val textEnabled: Boolean,
+    val tag: String
+)
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
new file mode 100644
index 0000000..7759f35
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.Node
+
+enum class LogLevel {
+    DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
+
+    companion object {
+        fun getLevelForMethodName(name: String, node: Node): LogLevel {
+            return when (name) {
+                "d" -> DEBUG
+                "v" -> VERBOSE
+                "i" -> INFO
+                "w" -> WARN
+                "e" -> ERROR
+                "wtf" -> WTF
+                else -> throw InvalidProtoLogCallException("Unknown log level $name", node)
+            }
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
new file mode 100644
index 0000000..a59038f
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonReader
+import com.android.server.protolog.common.InvalidFormatStringException
+import com.android.server.protolog.common.LogDataType
+import com.android.server.protolog.ProtoLogMessage
+import com.android.server.protolog.ProtoLogFileProto
+import java.io.BufferedReader
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.PrintStream
+import java.lang.Exception
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/**
+ * Implements a simple parser/viewer for binary ProtoLog logs.
+ * A binary log is translated into Android "LogCat"-like text log.
+ */
+class LogParser(private val configParser: ViewerConfigParser) {
+    companion object {
+        private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+        private val magicNumber =
+                ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+                        ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+    }
+
+    private fun printTime(time: Long, offset: Long, ps: PrintStream) {
+        ps.print(dateFormat.format(Date(time / 1000000 + offset)) + " ")
+    }
+
+    private fun printFormatted(
+        protoLogMessage: ProtoLogMessage,
+        configEntry: ViewerConfigParser.ConfigEntry,
+        ps: PrintStream
+    ) {
+        val strParmIt = protoLogMessage.strParamsList.iterator()
+        val longParamsIt = protoLogMessage.sint64ParamsList.iterator()
+        val doubleParamsIt = protoLogMessage.doubleParamsList.iterator()
+        val boolParamsIt = protoLogMessage.booleanParamsList.iterator()
+        val args = mutableListOf<Any>()
+        val format = configEntry.messageString
+        val argTypes = LogDataType.parseFormatString(format)
+        try {
+            argTypes.forEach {
+                when (it) {
+                    LogDataType.BOOLEAN -> args.add(boolParamsIt.next())
+                    LogDataType.LONG -> args.add(longParamsIt.next())
+                    LogDataType.DOUBLE -> args.add(doubleParamsIt.next())
+                    LogDataType.STRING -> args.add(strParmIt.next())
+                    null -> throw NullPointerException()
+                }
+            }
+        } catch (ex: NoSuchElementException) {
+            throw InvalidFormatStringException("Invalid format string in config", ex)
+        }
+        if (strParmIt.hasNext() || longParamsIt.hasNext() ||
+                doubleParamsIt.hasNext() || boolParamsIt.hasNext()) {
+            throw RuntimeException("Invalid format string in config - no enough matchers")
+        }
+        val formatted = format.format(*(args.toTypedArray()))
+        ps.print("${configEntry.level} ${configEntry.tag}: $formatted\n")
+    }
+
+    private fun printUnformatted(protoLogMessage: ProtoLogMessage, ps: PrintStream, tag: String) {
+        ps.println("$tag: ${protoLogMessage.messageHash} - ${protoLogMessage.strParamsList}" +
+                " ${protoLogMessage.sint64ParamsList} ${protoLogMessage.doubleParamsList}" +
+                " ${protoLogMessage.booleanParamsList}")
+    }
+
+    fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) {
+        val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput)))
+        val config = configParser.parseConfig(jsonReader)
+        val protoLog = ProtoLogFileProto.parseFrom(protoLogInput)
+
+        if (protoLog.magicNumber != magicNumber) {
+            throw InvalidInputException("ProtoLog file magic number is invalid.")
+        }
+        if (protoLog.version != Constants.VERSION) {
+            throw InvalidInputException("ProtoLog file version not supported by this tool," +
+                    " log version ${protoLog.version}, viewer version ${Constants.VERSION}")
+        }
+
+        protoLog.logList.forEach { log ->
+            printTime(log.elapsedRealtimeNanos, protoLog.realTimeToElapsedTimeOffsetMillis, ps)
+            if (log.messageHash !in config) {
+                printUnformatted(log, ps, "UNKNOWN")
+            } else {
+                val conf = config.getValue(log.messageHash)
+                try {
+                    printFormatted(log, conf, ps)
+                } catch (ex: Exception) {
+                    printUnformatted(log, ps, "INVALID")
+                }
+            }
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
new file mode 100644
index 0000000..eae6396
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+
+/**
+ * Helper class for visiting all ProtoLog calls.
+ * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
+ * is executed.
+ */
+open class ProtoLogCallProcessor(
+    private val protoLogClassName: String,
+    private val protoLogGroupClassName: String,
+    private val groupMap: Map<String, LogGroup>
+) {
+    private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+    private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
+
+    private fun getLogGroupName(
+        expr: Expression,
+        isClassImported: Boolean,
+        staticImports: Set<String>
+    ): String {
+        return when (expr) {
+            is NameExpr -> when {
+                expr.nameAsString in staticImports -> expr.nameAsString
+                else ->
+                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+            }
+            is FieldAccessExpr -> when {
+                expr.scope.toString() == protoLogGroupClassName
+                        || isClassImported &&
+                        expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
+                else ->
+                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+            }
+            else -> throw InvalidProtoLogCallException("Invalid group argument " +
+                    "- must be ProtoLogGroup enum member reference", expr)
+        }
+    }
+
+    private fun isProtoCall(
+        call: MethodCallExpr,
+        isLogClassImported: Boolean,
+        staticLogImports: Collection<String>
+    ): Boolean {
+        return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
+                isLogClassImported && call.scope.isPresent &&
+                call.scope.get().toString() == protoLogSimpleClassName ||
+                !call.scope.isPresent && staticLogImports.contains(call.name.toString())
+    }
+
+    open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?): CompilationUnit {
+        if (CodeUtils.isWildcardStaticImported(code, protoLogClassName) ||
+                CodeUtils.isWildcardStaticImported(code, protoLogGroupClassName)) {
+            throw IllegalImportException("Wildcard static imports of $protoLogClassName " +
+                    "and $protoLogGroupClassName methods are not supported.")
+        }
+
+        val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
+        val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
+        val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
+                protoLogGroupClassName)
+        val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
+
+        code.findAll(MethodCallExpr::class.java)
+                .filter { call ->
+                    isProtoCall(call, isLogClassImported, staticLogImports)
+                }.forEach { call ->
+                    if (call.arguments.size < 2) {
+                        throw InvalidProtoLogCallException("Method signature does not match " +
+                                "any ProtoLog method.", call)
+                    }
+
+                    val messageString = CodeUtils.concatMultilineString(call.getArgument(1))
+                    val groupNameArg = call.getArgument(0)
+                    val groupName =
+                            getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports)
+                    if (groupName !in groupMap) {
+                        throw InvalidProtoLogCallException("Unknown group argument " +
+                                "- not a ProtoLogGroup enum member", call)
+                    }
+
+                    callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
+                            call.name.toString(), call), groupMap.getValue(groupName))
+                }
+        return code
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
new file mode 100644
index 0000000..aa58b69
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.expr.MethodCallExpr
+
+interface ProtoLogCallVisitor {
+    fun processCall(call: MethodCallExpr, messageString: String, level: LogLevel, group: LogGroup)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
new file mode 100644
index 0000000..75493b6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.protolog.tool
+
+import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD
+import com.android.server.protolog.common.IProtoLogGroup
+import java.io.File
+import java.net.URLClassLoader
+
+class ProtoLogGroupReader {
+    private fun getClassloaderForJar(jarPath: String): ClassLoader {
+        val jarFile = File(jarPath)
+        val url = jarFile.toURI().toURL()
+        return URLClassLoader(arrayOf(url), ProtoLogGroupReader::class.java.classLoader)
+    }
+
+    private fun getEnumValues(clazz: Class<*>): List<IProtoLogGroup> {
+        val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD)
+        @Suppress("UNCHECKED_CAST")
+        return (valuesMethod.invoke(null) as Array<IProtoLogGroup>).toList()
+    }
+
+    fun loadFromJar(jarPath: String, className: String): Map<String, LogGroup> {
+        try {
+            val classLoader = getClassloaderForJar(jarPath)
+            val clazz = classLoader.loadClass(className)
+            val values = getEnumValues(clazz)
+            return values.map { group ->
+                group.name() to
+                        LogGroup(group.name(), group.isEnabled, group.isLogToLogcat, group.tag)
+            }.toMap()
+        } catch (ex: ReflectiveOperationException) {
+            throw RuntimeException("Unable to load ProtoLogGroup enum class", ex)
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
new file mode 100644
index 0000000..9678ec3
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.protolog.tool
+
+import com.android.protolog.tool.CommandOptions.Companion.USAGE
+import com.github.javaparser.StaticJavaParser
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.jar.JarOutputStream
+import java.util.zip.ZipEntry
+import kotlin.system.exitProcess
+
+object ProtoLogTool {
+    private fun showHelpAndExit() {
+        println(USAGE)
+        exitProcess(-1)
+    }
+
+    private fun containsProtoLogText(source: String, protoLogClassName: String): Boolean {
+        val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
+        return source.contains(protoLogSimpleClassName)
+    }
+
+    private fun processClasses(command: CommandOptions) {
+        val groups = ProtoLogGroupReader()
+                .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
+        val out = FileOutputStream(command.outputSourceJarArg)
+        val outJar = JarOutputStream(out)
+        val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+                command.protoLogGroupsClassNameArg, groups)
+        val transformer = SourceTransformer(command.protoLogImplClassNameArg, processor)
+
+        command.javaSourceArgs.forEach { path ->
+            val file = File(path)
+            val text = file.readText()
+            val code = StaticJavaParser.parse(text)
+            val outSrc = when {
+                containsProtoLogText(text, command.protoLogClassNameArg) ->
+                    transformer.processClass(text, code)
+                else -> text
+            }
+            val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
+                    .get().nameAsString else ""
+            val newPath = pack.replace('.', '/') + '/' + file.name
+            outJar.putNextEntry(ZipEntry(newPath))
+            outJar.write(outSrc.toByteArray())
+            outJar.closeEntry()
+        }
+
+        outJar.close()
+        out.close()
+    }
+
+    private fun viewerConf(command: CommandOptions) {
+        val groups = ProtoLogGroupReader()
+                .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
+        val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
+                command.protoLogGroupsClassNameArg, groups)
+        val builder = ViewerConfigBuilder(processor)
+        command.javaSourceArgs.forEach { path ->
+            val file = File(path)
+            val text = file.readText()
+            if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+                builder.processClass(StaticJavaParser.parse(text))
+            }
+        }
+        val out = FileOutputStream(command.viewerConfigJsonArg)
+        out.write(builder.build().toByteArray())
+        out.close()
+    }
+
+    private fun read(command: CommandOptions) {
+        LogParser(ViewerConfigParser())
+                .parse(FileInputStream(command.logProtofileArg),
+                        FileInputStream(command.viewerConfigJsonArg), System.out)
+    }
+
+    @JvmStatic
+    fun main(args: Array<String>) {
+        try {
+            val command = CommandOptions(args)
+            when (command.command) {
+                CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
+                CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
+                CommandOptions.READ_LOG_CMD -> read(command)
+            }
+        } catch (ex: InvalidCommandException) {
+            println(ex.message)
+            showHelpAndExit()
+        }
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
new file mode 100644
index 0000000..c392078
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -0,0 +1,228 @@
+/*
+ * 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.protolog.tool
+
+import com.android.protolog.tool.Constants.IS_LOG_TO_ANY_METHOD
+import com.android.server.protolog.common.LogDataType
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
+import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.CastExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.IntegerLiteralExpr
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
+import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.SimpleName
+import com.github.javaparser.ast.expr.TypeExpr
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
+import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ExpressionStmt
+import com.github.javaparser.ast.stmt.IfStmt
+import com.github.javaparser.ast.type.ArrayType
+import com.github.javaparser.ast.type.ClassOrInterfaceType
+import com.github.javaparser.ast.type.PrimitiveType
+import com.github.javaparser.ast.type.Type
+import com.github.javaparser.printer.PrettyPrinter
+import com.github.javaparser.printer.PrettyPrinterConfiguration
+import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter
+
+class SourceTransformer(
+    protoLogImplClassName: String,
+    private val protoLogCallProcessor: ProtoLogCallProcessor
+) : ProtoLogCallVisitor {
+    override fun processCall(
+        call: MethodCallExpr,
+        messageString: String,
+        level: LogLevel,
+        group: LogGroup
+    ) {
+        // Input format: ProtoLog.e(GROUP, "msg %d", arg)
+        if (!call.parentNode.isPresent) {
+            // Should never happen
+            throw RuntimeException("Unable to process log call $call " +
+                    "- no parent node in AST")
+        }
+        if (call.parentNode.get() !is ExpressionStmt) {
+            // Should never happen
+            throw RuntimeException("Unable to process log call $call " +
+                    "- parent node in AST is not an ExpressionStmt")
+        }
+        val parentStmt = call.parentNode.get() as ExpressionStmt
+        if (!parentStmt.parentNode.isPresent) {
+            // Should never happen
+            throw RuntimeException("Unable to process log call $call " +
+                    "- no grandparent node in AST")
+        }
+        val ifStmt: IfStmt
+        if (group.enabled) {
+            val hash = CodeUtils.hash(messageString, level)
+            val newCall = call.clone()
+            if (!group.textEnabled) {
+                // Remove message string if text logging is not enabled by default.
+                // Out: ProtoLog.e(GROUP, null, arg)
+                newCall.arguments[1].replace(NameExpr("null"))
+            }
+            // Insert message string hash as a second argument.
+            // Out: ProtoLog.e(GROUP, 1234, null, arg)
+            newCall.arguments.add(1, IntegerLiteralExpr(hash))
+            val argTypes = LogDataType.parseFormatString(messageString)
+            val typeMask = LogDataType.logDataTypesToBitMask(argTypes)
+            // Insert bitmap representing which Number parameters are to be considered as
+            // floating point numbers.
+            // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
+            newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
+            // Replace call to a stub method with an actual implementation.
+            // Out: com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, null, arg)
+            newCall.setScope(protoLogImplClassNode)
+            // Create a call to GROUP.isLogAny()
+            // Out: GROUP.isLogAny()
+            val isLogAnyExpr = MethodCallExpr(newCall.arguments[0].clone(),
+                    SimpleName(IS_LOG_TO_ANY_METHOD))
+            if (argTypes.size != call.arguments.size - 2) {
+                throw InvalidProtoLogCallException(
+                        "Number of arguments does not mach format string", call)
+            }
+            val blockStmt = BlockStmt()
+            if (argTypes.isNotEmpty()) {
+                // Assign every argument to a variable to check its type in compile time
+                // (this is assignment is optimized-out by dex tool, there is no runtime impact)/
+                // Out: long protoLogParam0 = arg
+                argTypes.forEachIndexed { idx, type ->
+                    val varName = "protoLogParam$idx"
+                    val declaration = VariableDeclarator(getASTTypeForDataType(type), varName,
+                            getConversionForType(type)(newCall.arguments[idx + 4].clone()))
+                    blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
+                    newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
+                }
+            } else {
+                // Assign (Object[])null as the vararg parameter to prevent allocating an empty
+                // object array.
+                val nullArray = CastExpr(ArrayType(objectType), NullLiteralExpr())
+                newCall.addArgument(nullArray)
+            }
+            blockStmt.addStatement(ExpressionStmt(newCall))
+            // Create an IF-statement with the previously created condition.
+            // Out: if (GROUP.isLogAny()) {
+            //          long protoLogParam0 = arg;
+            //          com.android.server.wm.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+            //      }
+            ifStmt = IfStmt(isLogAnyExpr, blockStmt, null)
+        } else {
+            // Surround with if (false).
+            val newCall = parentStmt.clone()
+            ifStmt = IfStmt(BooleanLiteralExpr(false), BlockStmt(NodeList(newCall)), null)
+            newCall.setBlockComment(" ${group.name} is disabled ")
+        }
+        // Inline the new statement.
+        val printedIfStmt = inlinePrinter.print(ifStmt)
+        // Append blank lines to preserve line numbering in file (to allow debugging)
+        val newLines = LexicalPreservingPrinter.print(parentStmt).count { c -> c == '\n' }
+        val newStmt = printedIfStmt.substringBeforeLast('}') + ("\n".repeat(newLines)) + '}'
+        // pre-workaround code, see explanation below
+        /*
+        val inlinedIfStmt = StaticJavaParser.parseStatement(newStmt)
+        LexicalPreservingPrinter.setup(inlinedIfStmt)
+        // Replace the original call.
+        if (!parentStmt.replace(inlinedIfStmt)) {
+            // Should never happen
+            throw RuntimeException("Unable to process log call $call " +
+                    "- unable to replace the call.")
+        }
+        */
+        /** Workaround for a bug in JavaParser (AST tree invalid after replacing a node when using
+         * LexicalPreservingPrinter (https://github.com/javaparser/javaparser/issues/2290).
+         * Replace the code below with the one commended-out above one the issue is resolved. */
+        if (!parentStmt.range.isPresent) {
+            // Should never happen
+            throw RuntimeException("Unable to process log call $call " +
+                    "- unable to replace the call.")
+        }
+        val range = parentStmt.range.get()
+        val begin = range.begin.line - 1
+        val oldLines = processedCode.subList(begin, range.end.line)
+        val oldCode = oldLines.joinToString("\n")
+        val newCode = oldCode.replaceRange(
+                offsets[begin] + range.begin.column - 1,
+                oldCode.length - oldLines.lastOrNull()!!.length +
+                        range.end.column + offsets[range.end.line - 1], newStmt)
+        newCode.split("\n").forEachIndexed { idx, line ->
+            offsets[begin + idx] += line.length - processedCode[begin + idx].length
+            processedCode[begin + idx] = line
+        }
+    }
+
+    private val inlinePrinter: PrettyPrinter
+    private val objectType = StaticJavaParser.parseClassOrInterfaceType("Object")
+
+    init {
+        val config = PrettyPrinterConfiguration()
+        config.endOfLineCharacter = " "
+        config.indentSize = 0
+        config.tabWidth = 1
+        inlinePrinter = PrettyPrinter(config)
+    }
+
+    companion object {
+        private val stringType: ClassOrInterfaceType =
+                StaticJavaParser.parseClassOrInterfaceType("String")
+
+        fun getASTTypeForDataType(type: Int): Type {
+            return when (type) {
+                LogDataType.STRING -> stringType.clone()
+                LogDataType.LONG -> PrimitiveType.longType()
+                LogDataType.DOUBLE -> PrimitiveType.doubleType()
+                LogDataType.BOOLEAN -> PrimitiveType.booleanType()
+                else -> {
+                    // Should never happen.
+                    throw RuntimeException("Invalid LogDataType")
+                }
+            }
+        }
+
+        fun getConversionForType(type: Int): (Expression) -> Expression {
+            return when (type) {
+                LogDataType.STRING -> { expr ->
+                    MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
+                            SimpleName("valueOf"), NodeList(expr))
+                }
+                else -> { expr -> expr }
+            }
+        }
+    }
+
+    private val protoLogImplClassNode =
+            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+    private var processedCode: MutableList<String> = mutableListOf()
+    private var offsets: IntArray = IntArray(0)
+
+    fun processClass(
+        code: String,
+        compilationUnit: CompilationUnit =
+               StaticJavaParser.parse(code)
+    ): String {
+        processedCode = code.split('\n').toMutableList()
+        offsets = IntArray(processedCode.size)
+        LexicalPreservingPrinter.setup(compilationUnit)
+        protoLogCallProcessor.process(compilationUnit, this)
+        // return LexicalPreservingPrinter.print(compilationUnit)
+        return processedCode.joinToString("\n")
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
new file mode 100644
index 0000000..a75b5c9
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonWriter
+import com.github.javaparser.ast.CompilationUnit
+import com.android.protolog.tool.Constants.VERSION
+import com.github.javaparser.ast.expr.MethodCallExpr
+import java.io.StringWriter
+
+class ViewerConfigBuilder(
+    private val protoLogCallVisitor: ProtoLogCallProcessor
+) : ProtoLogCallVisitor {
+    override fun processCall(
+        call: MethodCallExpr,
+        messageString: String,
+        level: LogLevel,
+        group: LogGroup
+    ) {
+        if (group.enabled) {
+            val key = CodeUtils.hash(messageString, level)
+            if (statements.containsKey(key)) {
+                if (statements[key] != Triple(messageString, level, group)) {
+                    throw HashCollisionException(
+                            "Please modify the log message \"$messageString\" " +
+                                    "or \"${statements[key]}\" - their hashes are equal.")
+                }
+            } else {
+                groups.add(group)
+                statements[key] = Triple(messageString, level, group)
+            }
+        }
+    }
+
+    private val statements: MutableMap<Int, Triple<String, LogLevel, LogGroup>> = mutableMapOf()
+    private val groups: MutableSet<LogGroup> = mutableSetOf()
+
+    fun processClass(unit: CompilationUnit) {
+        protoLogCallVisitor.process(unit, this)
+    }
+
+    fun build(): String {
+        val stringWriter = StringWriter()
+        val writer = JsonWriter(stringWriter)
+        writer.setIndent("  ")
+        writer.beginObject()
+        writer.name("version")
+        writer.value(VERSION)
+        writer.name("messages")
+        writer.beginObject()
+        statements.toSortedMap().forEach { (key, value) ->
+            writer.name(key.toString())
+            writer.beginObject()
+            writer.name("message")
+            writer.value(value.first)
+            writer.name("level")
+            writer.value(value.second.name)
+            writer.name("group")
+            writer.value(value.third.name)
+            writer.endObject()
+        }
+        writer.endObject()
+        writer.name("groups")
+        writer.beginObject()
+        groups.toSortedSet(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }).forEach { group ->
+            writer.name(group.name)
+            writer.beginObject()
+            writer.name("tag")
+            writer.value(group.tag)
+            writer.endObject()
+        }
+        writer.endObject()
+        writer.endObject()
+        stringWriter.buffer.append('\n')
+        return stringWriter.toString()
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
new file mode 100644
index 0000000..7278db0
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonReader
+
+open class ViewerConfigParser {
+    data class MessageEntry(
+        val messageString: String,
+        val level: String,
+        val groupName: String
+    )
+
+    fun parseMessage(jsonReader: JsonReader): MessageEntry {
+        jsonReader.beginObject()
+        var message: String? = null
+        var level: String? = null
+        var groupName: String? = null
+        while (jsonReader.hasNext()) {
+            when (jsonReader.nextName()) {
+                "message" -> message = jsonReader.nextString()
+                "level" -> level = jsonReader.nextString()
+                "group" -> groupName = jsonReader.nextString()
+                else -> jsonReader.skipValue()
+            }
+        }
+        jsonReader.endObject()
+        if (message.isNullOrBlank() || level.isNullOrBlank() || groupName.isNullOrBlank()) {
+            throw InvalidViewerConfigException("Invalid message entry in viewer config")
+        }
+        return MessageEntry(message, level, groupName)
+    }
+
+    data class GroupEntry(val tag: String)
+
+    fun parseGroup(jsonReader: JsonReader): GroupEntry {
+        jsonReader.beginObject()
+        var tag: String? = null
+        while (jsonReader.hasNext()) {
+            when (jsonReader.nextName()) {
+                "tag" -> tag = jsonReader.nextString()
+                else -> jsonReader.skipValue()
+            }
+        }
+        jsonReader.endObject()
+        if (tag.isNullOrBlank()) {
+            throw InvalidViewerConfigException("Invalid group entry in viewer config")
+        }
+        return GroupEntry(tag)
+    }
+
+    fun parseMessages(jsonReader: JsonReader): Map<Int, MessageEntry> {
+        val config: MutableMap<Int, MessageEntry> = mutableMapOf()
+        jsonReader.beginObject()
+        while (jsonReader.hasNext()) {
+            val key = jsonReader.nextName()
+            val hash = key.toIntOrNull()
+                    ?: throw InvalidViewerConfigException("Invalid key in messages viewer config")
+            config[hash] = parseMessage(jsonReader)
+        }
+        jsonReader.endObject()
+        return config
+    }
+
+    fun parseGroups(jsonReader: JsonReader): Map<String, GroupEntry> {
+        val config: MutableMap<String, GroupEntry> = mutableMapOf()
+        jsonReader.beginObject()
+        while (jsonReader.hasNext()) {
+            val key = jsonReader.nextName()
+            config[key] = parseGroup(jsonReader)
+        }
+        jsonReader.endObject()
+        return config
+    }
+
+    data class ConfigEntry(val messageString: String, val level: String, val tag: String)
+
+    open fun parseConfig(jsonReader: JsonReader): Map<Int, ConfigEntry> {
+        var messages: Map<Int, MessageEntry>? = null
+        var groups: Map<String, GroupEntry>? = null
+        var version: String? = null
+
+        jsonReader.beginObject()
+        while (jsonReader.hasNext()) {
+            when (jsonReader.nextName()) {
+                "messages" -> messages = parseMessages(jsonReader)
+                "groups" -> groups = parseGroups(jsonReader)
+                "version" -> version = jsonReader.nextString()
+
+                else -> jsonReader.skipValue()
+            }
+        }
+        jsonReader.endObject()
+        if (messages == null || groups == null || version == null) {
+            throw InvalidViewerConfigException("Invalid config - definitions missing")
+        }
+        if (version != Constants.VERSION) {
+            throw InvalidViewerConfigException("Viewer config version not supported by this tool," +
+                    " config version $version, viewer version ${Constants.VERSION}")
+        }
+        return messages.map { msg ->
+            msg.key to ConfigEntry(
+                    msg.value.messageString, msg.value.level, groups[msg.value.groupName]?.tag
+                    ?: throw InvalidViewerConfigException(
+                            "Group definition missing for ${msg.value.groupName}"))
+        }.toMap()
+    }
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
new file mode 100644
index 0000000..0401d8f
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.ast.Node
+import java.lang.Exception
+import java.lang.RuntimeException
+
+class HashCollisionException(message: String) : RuntimeException(message)
+
+class IllegalImportException(message: String) : Exception(message)
+
+class InvalidProtoLogCallException(message: String, node: Node)
+    : RuntimeException("$message\nAt: $node")
+
+class InvalidViewerConfigException(message: String) : Exception(message)
+
+class InvalidInputException(message: String) : Exception(message)
+
+class InvalidCommandException(message: String) : Exception(message)
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
new file mode 100644
index 0000000..337ed99
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.BinaryExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class CodeUtilsTest {
+    @Test
+    fun hash() {
+        assertEquals(-1704685243, CodeUtils.hash("test", LogLevel.DEBUG))
+    }
+
+    @Test
+    fun hash_changeLevel() {
+        assertEquals(-1176900998, CodeUtils.hash("test", LogLevel.ERROR))
+    }
+
+    @Test
+    fun hash_changeMessage() {
+        assertEquals(-1305634931, CodeUtils.hash("test2", LogLevel.DEBUG))
+    }
+
+    @Test
+    fun isWildcardStaticImported_true() {
+        val code = """package org.example.test;
+            import static org.example.Test.*;
+        """
+        assertTrue(CodeUtils.isWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test"))
+    }
+
+    @Test
+    fun isWildcardStaticImported_notStatic() {
+        val code = """package org.example.test;
+            import org.example.Test.*;
+        """
+        assertFalse(CodeUtils.isWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test"))
+    }
+
+    @Test
+    fun isWildcardStaticImported_differentClass() {
+        val code = """package org.example.test;
+            import static org.example.Test2.*;
+        """
+        assertFalse(CodeUtils.isWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test"))
+    }
+
+    @Test
+    fun isWildcardStaticImported_notWildcard() {
+        val code = """package org.example.test;
+            import org.example.Test.test;
+        """
+        assertFalse(CodeUtils.isWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test"))
+    }
+
+    @Test
+    fun isClassImportedOrSamePackage_imported() {
+        val code = """package org.example.test;
+            import org.example.Test;
+        """
+        assertTrue(CodeUtils.isClassImportedOrSamePackage(
+                StaticJavaParser.parse(code), "org.example.Test"))
+    }
+
+    @Test
+    fun isClassImportedOrSamePackage_samePackage() {
+        val code = """package org.example.test;
+        """
+        assertTrue(CodeUtils.isClassImportedOrSamePackage(
+                StaticJavaParser.parse(code), "org.example.test.Test"))
+    }
+
+    @Test
+    fun isClassImportedOrSamePackage_false() {
+        val code = """package org.example.test;
+            import org.example.Test;
+        """
+        assertFalse(CodeUtils.isClassImportedOrSamePackage(
+                StaticJavaParser.parse(code), "org.example.Test2"))
+    }
+
+    @Test
+    fun staticallyImportedMethods_ab() {
+        val code = """
+            import static org.example.Test.a;
+            import static org.example.Test.b;
+        """
+        val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+                "org.example.Test")
+        assertTrue(imported.containsAll(listOf("a", "b")))
+        assertEquals(2, imported.size)
+    }
+
+    @Test
+    fun staticallyImportedMethods_differentClass() {
+        val code = """
+            import static org.example.Test.a;
+            import static org.example.Test2.b;
+        """
+        val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+                "org.example.Test")
+        assertTrue(imported.containsAll(listOf("a")))
+        assertEquals(1, imported.size)
+    }
+
+    @Test
+    fun staticallyImportedMethods_notStatic() {
+        val code = """
+            import static org.example.Test.a;
+            import org.example.Test.b;
+        """
+        val imported = CodeUtils.staticallyImportedMethods(StaticJavaParser.parse(code),
+                "org.example.Test")
+        assertTrue(imported.containsAll(listOf("a")))
+        assertEquals(1, imported.size)
+    }
+
+    @Test
+    fun concatMultilineString_single() {
+        val str = StringLiteralExpr("test")
+        val out = CodeUtils.concatMultilineString(str)
+        assertEquals("test", out)
+    }
+
+    @Test
+    fun concatMultilineString_double() {
+        val str = """
+            "test" + "abc"
+        """
+        val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+        val out = CodeUtils.concatMultilineString(code)
+        assertEquals("testabc", out)
+    }
+
+    @Test
+    fun concatMultilineString_multiple() {
+        val str = """
+            "test" + "abc" + "1234" + "test"
+        """
+        val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
+        val out = CodeUtils.concatMultilineString(code)
+        assertEquals("testabc1234test", out)
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
new file mode 100644
index 0000000..615712e
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -0,0 +1,250 @@
+/*
+ * 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.protolog.tool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class CommandOptionsTest {
+    companion object {
+        val TEST_JAVA_SRC = listOf(
+                "frameworks/base/services/core/java/com/android/server/wm/" +
+                        "AccessibilityController.java",
+                "frameworks/base/services/core/java/com/android/server/wm/ActivityDisplay.java",
+                "frameworks/base/services/core/java/com/android/server/wm/" +
+                        "ActivityMetricsLaunchObserver.java"
+        )
+        private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
+        private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
+        private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
+        private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
+                "services/core/services.core.wm.protologgroups/android_common/javac/" +
+                "services.core.wm.protologgroups.jar"
+        private const val TEST_SRC_JAR = "out/soong/.temp/sbox175955373/" +
+                "services.core.wm.protolog.srcjar"
+        private const val TEST_VIEWER_JSON = "out/soong/.temp/sbox175955373/" +
+                "services.core.wm.protolog.json"
+        private const val TEST_LOG = "./test_log.pb"
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun noCommand() {
+        CommandOptions(arrayOf())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun invalidCommand() {
+        val testLine = "invalid"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test
+    fun transformClasses() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+        assertEquals(CommandOptions.TRANSFORM_CALLS_CMD, cmd.command)
+        assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+        assertEquals(TEST_PROTOLOGIMPL_CLASS, cmd.protoLogImplClassNameArg)
+        assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+        assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+        assertEquals(TEST_SRC_JAR, cmd.outputSourceJarArg)
+        assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogClass() {
+        val testLine = "transform-protolog-calls " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogImplClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogGroupClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogGroupJar() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noOutJar() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                TEST_JAVA_SRC.joinToString(" ")
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noJavaInput() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogClass() {
+        val testLine = "transform-protolog-calls --protolog-class invalid " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogImplClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class invalid " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogGroupClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class invalid " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogGroupJar() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar invalid.txt " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidOutJar() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidJavaInput() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR invalid.py"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_unknownParam() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noValue() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test
+    fun generateConfig() {
+        val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--viewer-conf $TEST_VIEWER_JSON ${TEST_JAVA_SRC.joinToString(" ")}"
+        val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+        assertEquals(CommandOptions.GENERATE_CONFIG_CMD, cmd.command)
+        assertEquals(TEST_PROTOLOG_CLASS, cmd.protoLogClassNameArg)
+        assertEquals(TEST_PROTOLOGGROUP_CLASS, cmd.protoLogGroupsClassNameArg)
+        assertEquals(TEST_PROTOLOGGROUP_JAR, cmd.protoLogGroupsJarArg)
+        assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+        assertEquals(TEST_JAVA_SRC, cmd.javaSourceArgs)
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun generateConfig_noViewerConfig() {
+        val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                TEST_JAVA_SRC.joinToString(" ")
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun generateConfig_invalidViewerConfig() {
+        val testLine = "generate-viewer-config --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--viewer-conf invalid.yaml ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test
+    fun readLog() {
+        val testLine = "read-log --viewer-conf $TEST_VIEWER_JSON $TEST_LOG"
+        val cmd = CommandOptions(testLine.split(' ').toTypedArray())
+        assertEquals(CommandOptions.READ_LOG_CMD, cmd.command)
+        assertEquals(TEST_VIEWER_JSON, cmd.viewerConfigJsonArg)
+        assertEquals(TEST_LOG, cmd.logProtofileArg)
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
new file mode 100644
index 0000000..04a3bfa
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonReader
+import com.android.server.protolog.ProtoLogMessage
+import com.android.server.protolog.ProtoLogFileProto
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.PrintStream
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class LogParserTest {
+    private val configParser: ViewerConfigParser = mock(ViewerConfigParser::class.java)
+    private val parser = LogParser(configParser)
+    private var config: MutableMap<Int, ViewerConfigParser.ConfigEntry> = mutableMapOf()
+    private var outStream: OutputStream = ByteArrayOutputStream()
+    private var printStream: PrintStream = PrintStream(outStream)
+    private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+    @Before
+    fun init() {
+        Mockito.`when`(configParser.parseConfig(any(JsonReader::class.java))).thenReturn(config)
+    }
+
+    private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+    private fun getConfigDummyStream(): InputStream {
+        return "".byteInputStream()
+    }
+
+    private fun buildProtoInput(logBuilder: ProtoLogFileProto.Builder): InputStream {
+        logBuilder.setVersion(Constants.VERSION)
+        logBuilder.magicNumber =
+                ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+                        ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+        return logBuilder.build().toByteArray().inputStream()
+    }
+
+    private fun testDate(timeMS: Long): String {
+        return dateFormat.format(Date(timeMS))
+    }
+
+    @Test
+    fun parse() {
+        config[70933285] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+                "ERROR", "WindowManager")
+
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        val logMessageBuilder = ProtoLogMessage.newBuilder()
+        logMessageBuilder
+                .setMessageHash(70933285)
+                .setElapsedRealtimeNanos(0)
+                .addBooleanParams(true)
+        logBuilder.addLog(logMessageBuilder.build())
+
+        parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+        assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: true\n",
+                outStream.toString())
+    }
+
+    @Test
+    fun parse_formatting() {
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+                " %x %e %g %s %f", "ERROR", "WindowManager")
+
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        val logMessageBuilder = ProtoLogMessage.newBuilder()
+        logMessageBuilder
+                .setMessageHash(123)
+                .setElapsedRealtimeNanos(0)
+                .addBooleanParams(true)
+                .addAllSint64Params(listOf(1000, 20000, 300000))
+                .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+                .addStrParams("test")
+        logBuilder.addLog(logMessageBuilder.build())
+
+        parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+        assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " +
+                "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n",
+                outStream.toString())
+    }
+
+    @Test
+    fun parse_invalidParamsTooMany() {
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o",
+                "ERROR", "WindowManager")
+
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        val logMessageBuilder = ProtoLogMessage.newBuilder()
+        logMessageBuilder
+                .setMessageHash(123)
+                .setElapsedRealtimeNanos(0)
+                .addBooleanParams(true)
+                .addAllSint64Params(listOf(1000, 20000, 300000))
+                .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1))
+                .addStrParams("test")
+        logBuilder.addLog(logMessageBuilder.build())
+
+        parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+        assertEquals("${testDate(0)} INVALID: 123 - [test] [1000, 20000, 300000] " +
+                "[0.1, 1.0E-5, 1000.1] [true]\n", outStream.toString())
+    }
+
+    @Test
+    fun parse_invalidParamsNotEnough() {
+        config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" +
+                " %x %e %g %s %f", "ERROR", "WindowManager")
+
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        val logMessageBuilder = ProtoLogMessage.newBuilder()
+        logMessageBuilder
+                .setMessageHash(123)
+                .setElapsedRealtimeNanos(0)
+                .addBooleanParams(true)
+                .addStrParams("test")
+        logBuilder.addLog(logMessageBuilder.build())
+
+        parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+        assertEquals("${testDate(0)} INVALID: 123 - [test] [] [] [true]\n",
+                outStream.toString())
+    }
+
+    @Test(expected = InvalidInputException::class)
+    fun parse_invalidMagicNumber() {
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        logBuilder.setVersion(Constants.VERSION)
+        logBuilder.magicNumber = 0
+        val stream = logBuilder.build().toByteArray().inputStream()
+
+        parser.parse(stream, getConfigDummyStream(), printStream)
+    }
+
+    @Test(expected = InvalidInputException::class)
+    fun parse_invalidVersion() {
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        logBuilder.setVersion("invalid")
+        logBuilder.magicNumber =
+                ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
+                        ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
+        val stream = logBuilder.build().toByteArray().inputStream()
+
+        parser.parse(stream, getConfigDummyStream(), printStream)
+    }
+
+    @Test
+    fun parse_noConfig() {
+        val logBuilder = ProtoLogFileProto.newBuilder()
+        val logMessageBuilder = ProtoLogMessage.newBuilder()
+        logMessageBuilder
+                .setMessageHash(70933285)
+                .setElapsedRealtimeNanos(0)
+                .addBooleanParams(true)
+        logBuilder.addLog(logMessageBuilder.build())
+
+        parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream)
+
+        assertEquals("${testDate(0)} UNKNOWN: 70933285 - [] [] [] [true]\n",
+                outStream.toString())
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
new file mode 100644
index 0000000..d20ce7e
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.expr.MethodCallExpr
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogCallProcessorTest {
+    private data class LogCall(
+        val call: MethodCallExpr,
+        val messageString: String,
+        val level: LogLevel,
+        val group: LogGroup
+    )
+
+    private val groupMap: MutableMap<String, LogGroup> = mutableMapOf()
+    private val calls: MutableList<LogCall> = mutableListOf()
+    private val visitor = ProtoLogCallProcessor("org.example.ProtoLog", "org.example.ProtoLogGroup",
+            groupMap)
+    private val processor = object : ProtoLogCallVisitor {
+        override fun processCall(
+            call: MethodCallExpr,
+            messageString: String,
+            level: LogLevel,
+            group: LogGroup
+        ) {
+            calls.add(LogCall(call, messageString, level, group))
+        }
+    }
+
+    private fun checkCalls() {
+        assertEquals(1, calls.size)
+        val c = calls[0]
+        assertEquals("test %b", c.messageString)
+        assertEquals(groupMap["TEST"], c.group)
+        assertEquals(LogLevel.DEBUG, c.level)
+    }
+
+    @Test
+    fun process_samePackage() {
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                    ProtoLog.e(ProtoLogGroup.ERROR, "error %d", 1);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
+        groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
+        visitor.process(StaticJavaParser.parse(code), processor)
+        assertEquals(2, calls.size)
+        var c = calls[0]
+        assertEquals("test %b", c.messageString)
+        assertEquals(groupMap["TEST"], c.group)
+        assertEquals(LogLevel.DEBUG, c.level)
+        c = calls[1]
+        assertEquals("error %d", c.messageString)
+        assertEquals(groupMap["ERROR"], c.group)
+        assertEquals(LogLevel.ERROR, c.level)
+    }
+
+    @Test
+    fun process_imported() {
+        val code = """
+            package org.example2;
+
+            import org.example.ProtoLog;
+            import org.example.ProtoLogGroup;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+        visitor.process(StaticJavaParser.parse(code), processor)
+        checkCalls()
+    }
+
+    @Test
+    fun process_importedStatic() {
+        val code = """
+            package org.example2;
+
+            import static org.example.ProtoLog.d;
+            import static org.example.ProtoLogGroup.TEST;
+
+            class Test {
+                void test() {
+                    d(TEST, "test %b", true);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+        visitor.process(StaticJavaParser.parse(code), processor)
+        checkCalls()
+    }
+
+    @Test(expected = InvalidProtoLogCallException::class)
+    fun process_groupNotImported() {
+        val code = """
+            package org.example2;
+
+            import org.example.ProtoLog;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+        visitor.process(StaticJavaParser.parse(code), processor)
+    }
+
+    @Test
+    fun process_protoLogNotImported() {
+        val code = """
+            package org.example2;
+
+            import org.example.ProtoLogGroup;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
+        visitor.process(StaticJavaParser.parse(code), processor)
+        assertEquals(0, calls.size)
+    }
+
+    @Test(expected = InvalidProtoLogCallException::class)
+    fun process_unknownGroup() {
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                }
+            }
+        """
+        visitor.process(StaticJavaParser.parse(code), processor)
+    }
+
+    @Test(expected = InvalidProtoLogCallException::class)
+    fun process_staticGroup() {
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(TEST, "test %b", true);
+                }
+            }
+        """
+        visitor.process(StaticJavaParser.parse(code), processor)
+    }
+
+    @Test(expected = InvalidProtoLogCallException::class)
+    fun process_badGroup() {
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(0, "test %b", true);
+                }
+            }
+        """
+        visitor.process(StaticJavaParser.parse(code), processor)
+    }
+
+    @Test(expected = InvalidProtoLogCallException::class)
+    fun process_invalidSignature() {
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d("test");
+                }
+            }
+        """
+        visitor.process(StaticJavaParser.parse(code), processor)
+    }
+
+    @Test
+    fun process_disabled() {
+        // Disabled groups are also processed.
+        val code = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.d(ProtoLogGroup.TEST, "test %b", true);
+                }
+            }
+        """
+        groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
+        visitor.process(StaticJavaParser.parse(code), processor)
+        checkCalls()
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
new file mode 100644
index 0000000..d6e4a36
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -0,0 +1,445 @@
+/*
+ * 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.protolog.tool
+
+import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.stmt.IfStmt
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.mockito.Mockito
+
+class SourceTransformerTest {
+    companion object {
+        private const val PROTO_LOG_IMPL_PATH = "org.example.ProtoLogImpl"
+
+        /* ktlint-disable max-line-length */
+        private val TEST_CODE = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_MULTILINE = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f " + 
+                    "abc %s\n test", 100,
+                     0.1, "test");
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_MULTICALLS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_NO_PARAMS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test");
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_NO_PARAMS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { org.example.ProtoLogImpl.w(TEST_GROUP, 1282022424, 0, "test", (Object[]) null); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, null, protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f " + "abc %s\n test", 100, 0.1, "test"); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+        /* ktlint-enable max-line-length */
+    }
+
+    private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+    private val sourceJarWriter = SourceTransformer("org.example.ProtoLogImpl", processor)
+
+    private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+    @Test
+    fun processClass_textEnabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_textEnabledMulticalls() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            val calls = code.findAll(MethodCallExpr::class.java)
+            visitor.processCall(calls[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+            visitor.processCall(calls[1], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+            visitor.processCall(calls[2], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(3, ifStmts.size)
+        val ifStmt = ifStmts[1]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_textEnabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(4, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(7, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("-986393606", methodCall.arguments[1].toString())
+        assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_noParams() {
+        var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(1, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(5, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("1282022424", methodCall.arguments[1].toString())
+        assertEquals(0.toString(), methodCall.arguments[2].toString())
+        assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
+    }
+
+    @Test
+    fun processClass_textDisabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("null", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_textDisabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    true, false, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(4, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(7, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("-986393606", methodCall.arguments[1].toString())
+        assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+        assertEquals("null", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_disabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", false, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("false", ifStmt.condition.toString())
+        assertEquals(TRANSFORMED_CODE_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_disabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    false, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("false", ifStmt.condition.toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_DISABLED, out)
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
new file mode 100644
index 0000000..f435d40
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonReader
+import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.expr.MethodCallExpr
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mockito.Mockito
+import java.io.StringReader
+
+class ViewerConfigBuilderTest {
+    companion object {
+        private val TAG1 = "WM_TEST"
+        private val TAG2 = "WM_DEBUG"
+        private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name, TAG1)
+        private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name, TAG2)
+        private val TEST3 = ViewerConfigParser.ConfigEntry("test3", LogLevel.ERROR.name, TAG2)
+    }
+
+    private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+    private val configBuilder = ViewerConfigBuilder(processor)
+    private val dummyCompilationUnit = CompilationUnit()
+
+    private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+    private fun parseConfig(json: String): Map<Int, ViewerConfigParser.ConfigEntry> {
+        return ViewerConfigParser().parseConfig(JsonReader(StringReader(json)))
+    }
+
+    @Test
+    fun processClass() {
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    LogGroup("TEST_GROUP", true, true, TAG1))
+            visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
+                    LogGroup("DEBUG_GROUP", true, true, TAG2))
+            visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
+                    LogGroup("DEBUG_GROUP", true, true, TAG2))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        configBuilder.processClass(dummyCompilationUnit)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(3, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString,
+                LogLevel.INFO)])
+        assertEquals(TEST2, parsedConfig[CodeUtils.hash(TEST2.messageString,
+                LogLevel.DEBUG)])
+        assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString,
+                LogLevel.ERROR)])
+    }
+
+    @Test
+    fun processClass_nonUnique() {
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    LogGroup("TEST_GROUP", true, true, TAG1))
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    LogGroup("TEST_GROUP", true, true, TAG1))
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    LogGroup("TEST_GROUP", true, true, TAG1))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        configBuilder.processClass(dummyCompilationUnit)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(1, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
+    }
+
+    @Test
+    fun processClass_disabled() {
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    LogGroup("TEST_GROUP", true, true, TAG1))
+            visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
+                    LogGroup("DEBUG_GROUP", false, true, TAG2))
+            visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
+                    LogGroup("DEBUG_GROUP", true, false, TAG2))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        configBuilder.processClass(dummyCompilationUnit)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(2, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(TEST1.messageString, LogLevel.INFO)])
+        assertEquals(TEST3, parsedConfig[CodeUtils.hash(TEST3.messageString, LogLevel.ERROR)])
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt
new file mode 100644
index 0000000..dc3ef7c
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt
@@ -0,0 +1,327 @@
+/*
+ * 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.protolog.tool
+
+import com.android.json.stream.JsonReader
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.io.StringReader
+
+class ViewerConfigParserTest {
+    private val parser = ViewerConfigParser()
+
+    private fun getJSONReader(str: String): JsonReader {
+        return JsonReader(StringReader(str))
+    }
+
+    @Test
+    fun parseMessage() {
+        val json = """
+        {
+            "message": "Test completed successfully: %b",
+            "level": "ERROR",
+            "group": "GENERIC_WM"
+        }
+        """
+        val msg = parser.parseMessage(getJSONReader(json))
+        assertEquals("Test completed successfully: %b", msg.messageString)
+        assertEquals("ERROR", msg.level)
+        assertEquals("GENERIC_WM", msg.groupName)
+    }
+
+    @Test
+    fun parseMessage_reorder() {
+        val json = """
+        {
+            "group": "GENERIC_WM",
+            "level": "ERROR",
+            "message": "Test completed successfully: %b"
+        }
+        """
+        val msg = parser.parseMessage(getJSONReader(json))
+        assertEquals("Test completed successfully: %b", msg.messageString)
+        assertEquals("ERROR", msg.level)
+        assertEquals("GENERIC_WM", msg.groupName)
+    }
+
+    @Test
+    fun parseMessage_unknownEntry() {
+        val json = """
+        {
+            "unknown": "unknown entries should not block parsing",
+            "message": "Test completed successfully: %b",
+            "level": "ERROR",
+            "group": "GENERIC_WM"
+        }
+        """
+        val msg = parser.parseMessage(getJSONReader(json))
+        assertEquals("Test completed successfully: %b", msg.messageString)
+        assertEquals("ERROR", msg.level)
+        assertEquals("GENERIC_WM", msg.groupName)
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseMessage_noMessage() {
+        val json = """
+        {
+            "level": "ERROR",
+            "group": "GENERIC_WM"
+        }
+        """
+        parser.parseMessage(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseMessage_noLevel() {
+        val json = """
+        {
+            "message": "Test completed successfully: %b",
+            "group": "GENERIC_WM"
+        }
+        """
+        parser.parseMessage(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseMessage_noGroup() {
+        val json = """
+        {
+            "message": "Test completed successfully: %b",
+            "level": "ERROR"
+        }
+        """
+        parser.parseMessage(getJSONReader(json))
+    }
+
+    @Test
+    fun parseGroup() {
+        val json = """
+        {
+            "tag": "WindowManager"
+        }
+        """
+        val group = parser.parseGroup(getJSONReader(json))
+        assertEquals("WindowManager", group.tag)
+    }
+
+    @Test
+    fun parseGroup_unknownEntry() {
+        val json = """
+        {
+            "unknown": "unknown entries should not block parsing",
+            "tag": "WindowManager"
+        }
+        """
+        val group = parser.parseGroup(getJSONReader(json))
+        assertEquals("WindowManager", group.tag)
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseGroup_noTag() {
+        val json = """
+        {
+        }
+        """
+        parser.parseGroup(getJSONReader(json))
+    }
+
+    @Test
+    fun parseMessages() {
+        val json = """
+        {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            },
+            "1792430067": {
+              "message": "Attempted to add window to a display that does not exist: %d. Aborting.",
+              "level": "WARN",
+              "group": "ERROR_WM"
+            }
+        }
+        """
+        val messages = parser.parseMessages(getJSONReader(json))
+        assertEquals(2, messages.size)
+        val msg1 =
+                ViewerConfigParser.MessageEntry("Test completed successfully: %b",
+                        "ERROR", "GENERIC_WM")
+        val msg2 =
+                ViewerConfigParser.MessageEntry("Attempted to add window to a display that " +
+                        "does not exist: %d. Aborting.", "WARN", "ERROR_WM")
+
+        assertEquals(msg1, messages[70933285])
+        assertEquals(msg2, messages[1792430067])
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseMessages_invalidHash() {
+        val json = """
+        {
+            "invalid": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+        }
+        """
+        parser.parseMessages(getJSONReader(json))
+    }
+
+    @Test
+    fun parseGroups() {
+        val json = """
+        {
+            "GENERIC_WM": {
+              "tag": "WindowManager"
+            },
+            "ERROR_WM": {
+              "tag": "WindowManagerError"
+            }
+        }
+        """
+        val groups = parser.parseGroups(getJSONReader(json))
+        assertEquals(2, groups.size)
+        val grp1 = ViewerConfigParser.GroupEntry("WindowManager")
+        val grp2 = ViewerConfigParser.GroupEntry("WindowManagerError")
+        assertEquals(grp1, groups["GENERIC_WM"])
+        assertEquals(grp2, groups["ERROR_WM"])
+    }
+
+    @Test
+    fun parseConfig() {
+        val json = """
+        {
+          "version": "${Constants.VERSION}",
+          "messages": {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+          },
+          "groups": {
+            "GENERIC_WM": {
+              "tag": "WindowManager"
+            }
+          }
+        }
+        """
+        val config = parser.parseConfig(getJSONReader(json))
+        assertEquals(1, config.size)
+        val cfg1 = ViewerConfigParser.ConfigEntry("Test completed successfully: %b",
+                "ERROR", "WindowManager")
+        assertEquals(cfg1, config[70933285])
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseConfig_invalidVersion() {
+        val json = """
+        {
+          "version": "invalid",
+          "messages": {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+          },
+          "groups": {
+            "GENERIC_WM": {
+              "tag": "WindowManager"
+            }
+          }
+        }
+        """
+        parser.parseConfig(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseConfig_noVersion() {
+        val json = """
+        {
+          "messages": {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+          },
+          "groups": {
+            "GENERIC_WM": {
+              "tag": "WindowManager"
+            }
+          }
+        }
+        """
+        parser.parseConfig(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseConfig_noMessages() {
+        val json = """
+        {
+          "version": "${Constants.VERSION}",
+          "groups": {
+            "GENERIC_WM": {
+              "tag": "WindowManager"
+            }
+          }
+        }
+        """
+        parser.parseConfig(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseConfig_noGroups() {
+        val json = """
+        {
+          "version": "${Constants.VERSION}",
+          "messages": {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+          }
+        }
+        """
+        parser.parseConfig(getJSONReader(json))
+    }
+
+    @Test(expected = InvalidViewerConfigException::class)
+    fun parseConfig_missingGroup() {
+        val json = """
+        {
+          "version": "${Constants.VERSION}",
+          "messages": {
+            "70933285": {
+              "message": "Test completed successfully: %b",
+              "level": "ERROR",
+              "group": "GENERIC_WM"
+            }
+          },
+          "groups": {
+            "ERROR_WM": {
+              "tag": "WindowManager"
+            }
+          }
+        }
+        """
+        parser.parseConfig(getJSONReader(json))
+    }
+}
diff --git a/wifi/java/android/net/wifi/IActionListener.aidl b/wifi/java/android/net/wifi/IActionListener.aidl
new file mode 100644
index 0000000..faa0901
--- /dev/null
+++ b/wifi/java/android/net/wifi/IActionListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.net.wifi;
+
+/**
+ * Interface for generic wifi callbacks.
+ * @hide
+ */
+oneway interface IActionListener
+{
+    void onSuccess();
+    void onFailure(int reason);
+}
diff --git a/wifi/java/android/net/wifi/ITxPacketCountListener.aidl b/wifi/java/android/net/wifi/ITxPacketCountListener.aidl
new file mode 100644
index 0000000..8606ab5
--- /dev/null
+++ b/wifi/java/android/net/wifi/ITxPacketCountListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.net.wifi;
+
+/**
+ * Interface for tx packet counter callback.
+ * @hide
+ */
+oneway interface ITxPacketCountListener
+{
+    void onSuccess(int count);
+    void onFailure(int reason);
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 931e5dd..a97a5a5 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -24,10 +24,12 @@
 
 import android.net.DhcpInfo;
 import android.net.Network;
+import android.net.wifi.IActionListener;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.ITxPacketCountListener;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
@@ -106,8 +108,6 @@
 
     int getWifiEnabledState();
 
-    void setCountryCode(String country);
-
     String getCountryCode();
 
     boolean isDualBandSupported();
@@ -156,8 +156,6 @@
 
     void notifyUserOfApBandConversion(String packageName);
 
-    Messenger getWifiServiceMessenger(String packageName);
-
     void enableTdls(String remoteIPAddress, boolean enable);
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
@@ -220,4 +218,12 @@
     void stopDppSession();
 
     void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec);
+
+    oneway void connect(in WifiConfiguration config, int netId, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+
+    oneway void save(in WifiConfiguration config, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+
+    oneway void forget(int netId, in IBinder binder, in IActionListener listener, int callbackIdentifier);
+
+    oneway void getTxPacketCount(String packageName, in IBinder binder, in ITxPacketCountListener listener, int callbackIdentifier);
 }
diff --git a/wifi/java/android/net/wifi/RssiPacketCountInfo.java b/wifi/java/android/net/wifi/RssiPacketCountInfo.java
deleted file mode 100644
index 4301165..0000000
--- a/wifi/java/android/net/wifi/RssiPacketCountInfo.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Bundle of RSSI and packet count information, for WiFi watchdog
- *
- * @see WifiWatchdogStateMachine
- *
- * @hide
- */
-public class RssiPacketCountInfo implements Parcelable {
-
-    public int rssi;
-
-    public int txgood;
-
-    public int txbad;
-
-    public int rxgood;
-
-    public RssiPacketCountInfo() {
-        rssi = txgood = txbad = rxgood = 0;
-    }
-
-    private RssiPacketCountInfo(Parcel in) {
-        rssi = in.readInt();
-        txgood = in.readInt();
-        txbad = in.readInt();
-        rxgood = in.readInt();
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(rssi);
-        out.writeInt(txgood);
-        out.writeInt(txbad);
-        out.writeInt(rxgood);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<RssiPacketCountInfo> CREATOR =
-            new Parcelable.Creator<RssiPacketCountInfo>() {
-        @Override
-        public RssiPacketCountInfo createFromParcel(Parcel in) {
-            return new RssiPacketCountInfo(in);
-        }
-
-        @Override
-        public RssiPacketCountInfo[] newArray(int size) {
-            return new RssiPacketCountInfo[size];
-        }
-    };
-}
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 6ce2121..6a03c73 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -211,6 +211,7 @@
         /** Draft 11mc version supported, including major and minor version. e.g, draft 4.3 is 43 */
         public int mcVersion;
 
+        @NonNull
         @Override
         public String toString() {
             StringBuffer sb = new StringBuffer();
@@ -1130,6 +1131,7 @@
          */
         public int preamble;
 
+        @NonNull
         @Override
         public String toString() {
             StringBuilder builder = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index f2ae447..eb5a717 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageManager;
@@ -510,6 +511,12 @@
     public String preSharedKey;
 
     /**
+     * Optional SAE Password Id for use with WPA3-SAE. It is an ASCII string.
+     * @hide
+     */
+    public @Nullable String saePasswordId;
+
+    /**
      * Four WEP keys. For each of the four values, provide either an ASCII
      * string enclosed in double quotation marks (e.g., {@code "abcdef"}),
      * a string of hex digits (e.g., {@code 0102030405}), or an empty string
@@ -2004,6 +2011,9 @@
             sbuf.append('*');
         }
 
+        sbuf.append('\n').append(" SAE Password Id: ");
+        sbuf.append(this.saePasswordId);
+
         sbuf.append("\nEnterprise config:\n");
         sbuf.append(enterpriseConfig);
 
@@ -2372,6 +2382,7 @@
             providerFriendlyName = source.providerFriendlyName;
             isHomeProviderNetwork = source.isHomeProviderNetwork;
             preSharedKey = source.preSharedKey;
+            saePasswordId = source.saePasswordId;
 
             mNetworkSelectionStatus.copy(source.getNetworkSelectionStatus());
             apBand = source.apBand;
@@ -2462,6 +2473,7 @@
             dest.writeLong(roamingConsortiumId);
         }
         dest.writeString(preSharedKey);
+        dest.writeString(saePasswordId);
         for (String wepKey : wepKeys) {
             dest.writeString(wepKey);
         }
@@ -2537,6 +2549,7 @@
                     config.roamingConsortiumIds[i] = in.readLong();
                 }
                 config.preSharedKey = in.readString();
+                config.saePasswordId = in.readString();
                 for (int i = 0; i < config.wepKeys.length; i++) {
                     config.wepKeys[i] = in.readString();
                 }
@@ -2608,6 +2621,7 @@
         out.writeInt(apBand);
         out.writeInt(apChannel);
         BackupUtils.writeString(out, preSharedKey);
+        BackupUtils.writeString(out, saePasswordId);
         out.writeInt(getAuthType());
         out.writeBoolean(hiddenSSID);
         return baos.toByteArray();
@@ -2631,6 +2645,7 @@
         config.apBand = in.readInt();
         config.apChannel = in.readInt();
         config.preSharedKey = BackupUtils.readString(in);
+        config.saePasswordId = BackupUtils.readString(in);
         config.allowedKeyManagement.set(in.readInt());
         if (version >= 3) {
             config.hiddenSSID = in.readBoolean();
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 9d4b837..f8c2011 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -15,6 +15,7 @@
  */
 package android.net.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -23,6 +24,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -111,6 +114,48 @@
     /** @hide */
     public static final String CA_CERT_ALIAS_DELIMITER = " ";
 
+    /**
+     * Do not use OCSP stapling (TLS certificate status extension)
+     * @hide
+     */
+    public static final int OCSP_NONE = 0;
+
+    /**
+     * Try to use OCSP stapling, but not require response
+     * @hide
+     */
+    public static final int OCSP_REQUEST_CERT_STATUS = 1;
+
+    /**
+     * Require valid OCSP stapling response
+     * @hide
+     */
+    public static final int OCSP_REQUIRE_CERT_STATUS = 2;
+
+    /**
+     * Require valid OCSP stapling response for all not-trusted certificates in the server
+     * certificate chain
+     * @hide
+     */
+    public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"OCSP_"}, value = {
+            OCSP_NONE,
+            OCSP_REQUEST_CERT_STATUS,
+            OCSP_REQUIRE_CERT_STATUS,
+            OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Ocsp {
+    }
+
+    /**
+     * Whether to use/require OCSP (Online Certificate Status Protocol) to check server certificate.
+     * @hide
+     */
+    private @Ocsp int mOcsp = OCSP_NONE;
+
     // Fields to copy verbatim from wpa_supplicant.
     private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] {
             IDENTITY_KEY,
@@ -185,6 +230,7 @@
         mPhase2Method = source.mPhase2Method;
         mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
         mIsAppInstalledCaCert = source.mIsAppInstalledCaCert;
+        mOcsp = source.mOcsp;
     }
 
     /**
@@ -230,6 +276,7 @@
         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
         dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
         dest.writeBoolean(mIsAppInstalledCaCert);
+        dest.writeInt(mOcsp);
     }
 
     public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR =
@@ -251,6 +298,7 @@
                     enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
                     enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
+                    enterpriseConfig.mOcsp = in.readInt();
                     return enterpriseConfig;
                 }
 
@@ -1141,6 +1189,7 @@
         if (mPhase2Method > 0 && mPhase2Method < Phase2.strings.length) {
             sb.append("phase2_method: ").append(Phase2.strings[mPhase2Method]).append("\n");
         }
+        sb.append(" ocsp: ").append(mOcsp).append("\n");
         return sb.toString();
     }
 
@@ -1190,4 +1239,28 @@
     public boolean isAppInstalledCaCert() {
         return mIsAppInstalledCaCert;
     }
+
+    /**
+     * Set the ocsp type.
+     * @param  ocsp is one {@link ##OCSP_NONE}, {@link #OCSP_REQUEST_CERT_STATUS},
+     *                   {@link #OCSP_REQUIRE_CERT_STATUS} or
+     *                   {@link #OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS}
+     * @hide
+     */
+    public void setOcsp(@Ocsp int ocsp) {
+        if (ocsp >= OCSP_NONE && ocsp <= OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS) {
+            mOcsp = ocsp;
+        } else {
+            throw new IllegalArgumentException("Invalid OCSP type.");
+        }
+    }
+
+    /**
+     * Get the ocsp type.
+     * @return ocsp type
+     * @hide
+     */
+    public @Ocsp int getOcsp() {
+        return mOcsp;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6bf7bfb9..00895e8 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -52,14 +52,13 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.WorkSource;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.Protocol;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.net.NetworkPinner;
 
 import dalvik.system.CloseGuard;
@@ -73,7 +72,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 
 /**
@@ -1126,13 +1124,6 @@
     IWifiManager mService;
     private final int mTargetSdkVersion;
 
-    private static final int INVALID_KEY = 0;
-    private int mListenerKey = 1;
-    private final SparseArray mListenerMap = new SparseArray();
-    private final Object mListenerMapLock = new Object();
-
-    private AsyncChannel mAsyncChannel;
-    private CountDownLatch mConnected;
     private Looper mLooper;
     private boolean mVerboseLoggingEnabled = false;
 
@@ -1730,13 +1721,12 @@
      * <li> When an app is uninstalled, all its suggested networks are discarded. If the device is
      * currently connected to a suggested network which is being removed then the device will
      * disconnect from that network.</li>
-     * <li> No in-place modification of existing suggestions are allowed. Apps are expected to
-     * remove suggestions using {@link #removeNetworkSuggestions(List)} and then add the modified
-     * suggestion back using this API.</li>
+     * <li> In-place modification of existing suggestions are allowed.
+     * If the provided suggestions {@link WifiNetworkSuggestion#equals(Object)} any previously
+     * provided suggestions by the app. Previous suggestions will be updated</li>
      *
      * @param networkSuggestions List of network suggestions provided by the app.
      * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
-     * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
      * @throws {@link SecurityException} if the caller is missing required permissions.
      */
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
@@ -1821,6 +1811,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (config == null) return;
                 throw new RemoteException("Wifi service is not running");
             }
             if (!iWifiManager.addOrUpdatePasspointConfiguration(
@@ -1849,6 +1840,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(fqdn)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             if (!iWifiManager.removePasspointConfiguration(
@@ -1899,6 +1891,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (bssid == 0L || TextUtils.isEmpty(fileName)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.queryPasspointIcon(bssid, fileName);
@@ -1928,6 +1921,8 @@
      * @param holdoff hold off time in milliseconds
      * @param ess set if the hold off pertains to an ESS rather than a BSS
      * @hide
+     *
+     * TODO (140167680): This needs to be removed, the implementation is empty!
      */
     public void deauthenticateNetwork(long holdoff, boolean ess) {
         try {
@@ -2495,24 +2490,6 @@
     }
 
     /**
-     * Set the country code.
-     * @param countryCode country code in ISO 3166 format.
-     *
-     * @hide
-     */
-    public void setCountryCode(@NonNull String country) {
-        try {
-            IWifiManager iWifiManager = getIWifiManager();
-            if (iWifiManager == null) {
-                throw new RemoteException("Wifi service is not running");
-            }
-            iWifiManager.setCountryCode(country);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
     * get the country code.
     * @return the country code in ISO 3166 format.
     *
@@ -2637,8 +2614,23 @@
      *
      * @hide for CTS test only
      */
-    public void getTxPacketCount(TxPacketCountListener listener) {
-        getChannel().sendMessage(RSSI_PKTCNT_FETCH, 0, putListener(listener));
+    public void getTxPacketCount(@NonNull TxPacketCountListener listener) {
+        if (listener == null) throw new IllegalArgumentException("listener cannot be null");
+        Binder binder = new Binder();
+        TxPacketCountListenerProxy listenerProxy =
+                new TxPacketCountListenerProxy(mLooper, listener);
+        try {
+            IWifiManager iWifiManager = getIWifiManager();
+            if (iWifiManager == null) {
+                throw new RemoteException("Wifi service is not running");
+            }
+            iWifiManager.getTxPacketCount(mContext.getOpPackageName(), binder, listenerProxy,
+                    listener.hashCode());
+        } catch (RemoteException e) {
+            listenerProxy.onFailure(ERROR);
+        } catch (SecurityException e) {
+            listenerProxy.onFailure(NOT_AUTHORIZED);
+        }
     }
 
     /**
@@ -2692,6 +2684,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(ifaceName) || mode == IFACE_IP_MODE_UNSPECIFIED) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.updateInterfaceIpState(ifaceName, mode);
@@ -3044,6 +3037,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (remoteIPAddress == null || !enable) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableTdls(remoteIPAddress.getHostAddress(), enable);
@@ -3062,6 +3056,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(remoteMacAddress) || !enable) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableTdlsWithMacAddress(remoteMacAddress, enable);
@@ -3070,76 +3065,6 @@
         }
     }
 
-    /* TODO: deprecate synchronous API and open up the following API */
-
-    private static final int BASE = Protocol.BASE_WIFI_MANAGER;
-
-    /* Commands to WifiService */
-    /** @hide */
-    public static final int CONNECT_NETWORK                 = BASE + 1;
-    /** @hide */
-    public static final int CONNECT_NETWORK_FAILED          = BASE + 2;
-    /** @hide */
-    public static final int CONNECT_NETWORK_SUCCEEDED       = BASE + 3;
-
-    /** @hide */
-    public static final int FORGET_NETWORK                  = BASE + 4;
-    /** @hide */
-    public static final int FORGET_NETWORK_FAILED           = BASE + 5;
-    /** @hide */
-    public static final int FORGET_NETWORK_SUCCEEDED        = BASE + 6;
-
-    /** @hide */
-    public static final int SAVE_NETWORK                    = BASE + 7;
-    /** @hide */
-    public static final int SAVE_NETWORK_FAILED             = BASE + 8;
-    /** @hide */
-    public static final int SAVE_NETWORK_SUCCEEDED          = BASE + 9;
-
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int START_WPS                       = BASE + 10;
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int START_WPS_SUCCEEDED             = BASE + 11;
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int WPS_FAILED                      = BASE + 12;
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int WPS_COMPLETED                   = BASE + 13;
-
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int CANCEL_WPS                      = BASE + 14;
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int CANCEL_WPS_FAILED               = BASE + 15;
-    /** @hide
-     * @deprecated This is deprecated
-     */
-    public static final int CANCEL_WPS_SUCCEDED             = BASE + 16;
-
-    /** @hide */
-    public static final int DISABLE_NETWORK                 = BASE + 17;
-    /** @hide */
-    public static final int DISABLE_NETWORK_FAILED          = BASE + 18;
-    /** @hide */
-    public static final int DISABLE_NETWORK_SUCCEEDED       = BASE + 19;
-
-    /** @hide */
-    public static final int RSSI_PKTCNT_FETCH               = BASE + 20;
-    /** @hide */
-    public static final int RSSI_PKTCNT_FETCH_SUCCEEDED     = BASE + 21;
-    /** @hide */
-    public static final int RSSI_PKTCNT_FETCH_FAILED        = BASE + 22;
-
     /**
      * Passed with {@link ActionListener#onFailure}.
      * Indicates that the operation failed due to an internal error.
@@ -3162,6 +3087,11 @@
      */
     public static final int BUSY                        = 2;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR, IN_PROGRESS, BUSY})
+    public @interface ActionListenerFailureReason {}
+
     /* WPS specific errors */
     /** WPS overlap detected
      * @deprecated This is deprecated
@@ -3206,20 +3136,13 @@
     public interface ActionListener {
         /**
          * The operation succeeded.
-         * This is called when the scan request has been validated and ready
-         * to sent to driver.
          */
-        public void onSuccess();
+        void onSuccess();
         /**
          * The operation failed.
-         * This is called when the scan request failed.
-         * @param reason The reason for failure could be one of the following:
-         * {@link #REASON_INVALID_REQUEST}} is specified when scan request parameters are invalid.
-         * {@link #REASON_NOT_AUTHORIZED} is specified when requesting app doesn't have the required
-         * permission to request a scan.
-         * {@link #REASON_UNSPECIFIED} is specified when driver reports a scan failure.
+         * @param reason The reason for failure depends on the operation.
          */
-        public void onFailure(int reason);
+        void onFailure(@ActionListenerFailureReason int reason);
     }
 
     /** Interface for callback invocation on a start WPS action
@@ -3264,6 +3187,41 @@
     }
 
     /**
+     * Callback proxy for TxPacketCountListener objects.
+     *
+     * @hide
+     */
+    private class TxPacketCountListenerProxy extends ITxPacketCountListener.Stub {
+        private final Handler mHandler;
+        private final TxPacketCountListener mCallback;
+
+        TxPacketCountListenerProxy(Looper looper, TxPacketCountListener callback) {
+            mHandler = new Handler(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSuccess(int count) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "TxPacketCounterProxy: onSuccess: count=" + count);
+            }
+            mHandler.post(() -> {
+                mCallback.onSuccess(count);
+            });
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "TxPacketCounterProxy: onFailure: reason=" + reason);
+            }
+            mHandler.post(() -> {
+                mCallback.onFailure(reason);
+            });
+        }
+    }
+
+    /**
      * Base class for soft AP callback. Should be extended by applications and set when calling
      * {@link WifiManager#registerSoftApCallback(SoftApCallback, Handler)}.
      *
@@ -3697,125 +3655,61 @@
         }
     }
 
-    // Ensure that multiple ServiceHandler threads do not interleave message dispatch.
-    private static final Object sServiceHandlerDispatchLock = new Object();
+    /**
+     * Callback proxy for ActionListener objects.
+     */
+    private class ActionListenerProxy extends IActionListener.Stub {
+        private final String mActionTag;
+        private final Handler mHandler;
+        private final ActionListener mCallback;
 
-    private class ServiceHandler extends Handler {
-        ServiceHandler(Looper looper) {
-            super(looper);
+        ActionListenerProxy(String actionTag, Looper looper, ActionListener callback) {
+            mActionTag = actionTag;
+            mHandler = new Handler(looper);
+            mCallback = callback;
         }
 
         @Override
-        public void handleMessage(Message message) {
-            synchronized (sServiceHandlerDispatchLock) {
-                dispatchMessageToListeners(message);
+        public void onSuccess() {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "ActionListenerProxy:" + mActionTag + ": onSuccess");
             }
+            mHandler.post(() -> {
+                mCallback.onSuccess();
+            });
         }
 
-        private void dispatchMessageToListeners(Message message) {
-            Object listener = removeListener(message.arg2);
-            switch (message.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                    } else {
-                        Log.e(TAG, "Failed to set up channel connection");
-                        // This will cause all further async API calls on the WifiManager
-                        // to fail and throw an exception
-                        mAsyncChannel = null;
-                    }
-                    mConnected.countDown();
-                    break;
-                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                    // Ignore
-                    break;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    Log.e(TAG, "Channel connection lost");
-                    // This will cause all further async API calls on the WifiManager
-                    // to fail and throw an exception
-                    mAsyncChannel = null;
-                    getLooper().quit();
-                    break;
-                    /* ActionListeners grouped together */
-                case WifiManager.CONNECT_NETWORK_FAILED:
-                case WifiManager.FORGET_NETWORK_FAILED:
-                case WifiManager.SAVE_NETWORK_FAILED:
-                case WifiManager.DISABLE_NETWORK_FAILED:
-                    if (listener != null) {
-                        ((ActionListener) listener).onFailure(message.arg1);
-                    }
-                    break;
-                    /* ActionListeners grouped together */
-                case WifiManager.CONNECT_NETWORK_SUCCEEDED:
-                case WifiManager.FORGET_NETWORK_SUCCEEDED:
-                case WifiManager.SAVE_NETWORK_SUCCEEDED:
-                case WifiManager.DISABLE_NETWORK_SUCCEEDED:
-                    if (listener != null) {
-                        ((ActionListener) listener).onSuccess();
-                    }
-                    break;
-                case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
-                    if (listener != null) {
-                        RssiPacketCountInfo info = (RssiPacketCountInfo) message.obj;
-                        if (info != null)
-                            ((TxPacketCountListener) listener).onSuccess(info.txgood + info.txbad);
-                        else
-                            ((TxPacketCountListener) listener).onFailure(ERROR);
-                    }
-                    break;
-                case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
-                    if (listener != null) {
-                        ((TxPacketCountListener) listener).onFailure(message.arg1);
-                    }
-                    break;
-                default:
-                    //ignore
-                    break;
+        @Override
+        public void onFailure(@ActionListenerFailureReason int reason) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "ActionListenerProxy:" + mActionTag + ": onFailure=" + reason);
             }
+            mHandler.post(() -> {
+                mCallback.onFailure(reason);
+            });
         }
     }
 
-    private int putListener(Object listener) {
-        if (listener == null) return INVALID_KEY;
-        int key;
-        synchronized (mListenerMapLock) {
-            do {
-                key = mListenerKey++;
-            } while (key == INVALID_KEY);
-            mListenerMap.put(key, listener);
+    private void connectInternal(@Nullable WifiConfiguration config, int networkId,
+            @Nullable ActionListener listener) {
+        ActionListenerProxy listenerProxy = null;
+        Binder binder = null;
+        if (listener != null) {
+            listenerProxy = new ActionListenerProxy("connect", mLooper, listener);
+            binder = new Binder();
         }
-        return key;
-    }
-
-    private Object removeListener(int key) {
-        if (key == INVALID_KEY) return null;
-        synchronized (mListenerMapLock) {
-            Object listener = mListenerMap.get(key);
-            mListenerMap.remove(key);
-            return listener;
-        }
-    }
-
-    private synchronized AsyncChannel getChannel() {
-        if (mAsyncChannel == null) {
-            Messenger messenger = getWifiServiceMessenger();
-            if (messenger == null) {
-                throw new IllegalStateException(
-                        "getWifiServiceMessenger() returned null!  This is invalid.");
+        try {
+            IWifiManager iWifiManager = getIWifiManager();
+            if (iWifiManager == null) {
+                throw new RemoteException("Wifi service is not running");
             }
-
-            mAsyncChannel = new AsyncChannel();
-            mConnected = new CountDownLatch(1);
-
-            Handler handler = new ServiceHandler(mLooper);
-            mAsyncChannel.connect(mContext, handler, messenger);
-            try {
-                mConnected.await();
-            } catch (InterruptedException e) {
-                Log.e(TAG, "interrupted wait at init");
-            }
+            iWifiManager.connect(config, networkId, binder, listenerProxy,
+                    listener == null ? 0 : listener.hashCode());
+        } catch (RemoteException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(ERROR);
+        } catch (SecurityException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(NOT_AUTHORIZED);
         }
-        return mAsyncChannel;
     }
 
     /**
@@ -3841,10 +3735,7 @@
     })
     public void connect(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
-        // Use INVALID_NETWORK_ID for arg1 when passing a config object
-        // arg1 is used to pass network id when the network already exists
-        getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
-                putListener(listener), config);
+        connectInternal(config, WifiConfiguration.INVALID_NETWORK_ID, listener);
     }
 
     /**
@@ -3867,7 +3758,7 @@
     })
     public void connect(int networkId, @Nullable ActionListener listener) {
         if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
-        getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
+        connectInternal(null, networkId, listener);
     }
 
     /**
@@ -3898,7 +3789,24 @@
     })
     public void save(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
-        getChannel().sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
+        ActionListenerProxy listenerProxy = null;
+        Binder binder = null;
+        if (listener != null) {
+            listenerProxy = new ActionListenerProxy("save", mLooper, listener);
+            binder = new Binder();
+        }
+        try {
+            IWifiManager iWifiManager = getIWifiManager();
+            if (iWifiManager == null) {
+                throw new RemoteException("Wifi service is not running");
+            }
+            iWifiManager.save(config, binder, listenerProxy,
+                    listener == null ? 0 : listener.hashCode());
+        } catch (RemoteException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(ERROR);
+        } catch (SecurityException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(NOT_AUTHORIZED);
+        }
     }
 
     /**
@@ -3922,7 +3830,24 @@
     })
     public void forget(int netId, @Nullable ActionListener listener) {
         if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
-        getChannel().sendMessage(FORGET_NETWORK, netId, putListener(listener));
+        ActionListenerProxy listenerProxy = null;
+        Binder binder = null;
+        if (listener != null) {
+            listenerProxy = new ActionListenerProxy("forget", mLooper, listener);
+            binder = new Binder();
+        }
+        try {
+            IWifiManager iWifiManager = getIWifiManager();
+            if (iWifiManager == null) {
+                throw new RemoteException("Wifi service is not running");
+            }
+            iWifiManager.forget(netId, binder, listenerProxy,
+                    listener == null ? 0 : listener.hashCode());
+        } catch (RemoteException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(ERROR);
+        } catch (SecurityException e) {
+            if (listenerProxy != null) listenerProxy.onFailure(NOT_AUTHORIZED);
+        }
     }
 
     /**
@@ -3932,6 +3857,7 @@
      * @param listener for callbacks on success or failure. Can be null.
      * @throws IllegalStateException if the WifiManager instance needs to be
      * initialized again
+     * @deprecated This API is deprecated. Use {@link #disableNetwork(int)} instead.
      * @hide
      */
     @SystemApi
@@ -3940,9 +3866,19 @@
             android.Manifest.permission.NETWORK_SETUP_WIZARD,
             android.Manifest.permission.NETWORK_STACK
     })
+    @Deprecated
     public void disable(int netId, @Nullable ActionListener listener) {
         if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
-        getChannel().sendMessage(DISABLE_NETWORK, netId, putListener(listener));
+        // Simple wrapper which forwards the call to disableNetwork. This is a temporary
+        // implementation until we can remove this API completely.
+        boolean status = disableNetwork(netId);
+        if (listener != null) {
+            if (status) {
+                listener.onSuccess();
+            } else {
+                listener.onFailure(ERROR);
+            }
+        }
     }
 
     /**
@@ -3956,7 +3892,7 @@
             android.Manifest.permission.NETWORK_STACK
     })
     public void disableEphemeralNetwork(String SSID) {
-        if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
+        if (TextUtils.isEmpty(SSID)) throw new IllegalArgumentException("SSID cannot be null");
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
@@ -3998,24 +3934,6 @@
     }
 
     /**
-     * Get a reference to WifiService handler. This is used by a client to establish
-     * an AsyncChannel communication with WifiService
-     *
-     * @return Messenger pointing to the WifiService handler
-     */
-    @UnsupportedAppUsage
-    private Messenger getWifiServiceMessenger() {
-        try {
-            IWifiManager iWifiManager = getIWifiManager();
-            if (iWifiManager == null) return null;
-            return iWifiManager.getWifiServiceMessenger(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-
-    /**
      * Allows an application to keep the Wi-Fi radio awake.
      * Normally the Wi-Fi radio may turn off when the user has not used the device in a while.
      * Acquiring a WifiLock will keep the radio on until the lock is released.  Multiple
@@ -4475,16 +4393,6 @@
         }
     }
 
-    protected void finalize() throws Throwable {
-        try {
-            if (mAsyncChannel != null) {
-                mAsyncChannel.disconnect();
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
     /**
      * Set wifi verbose log. Called from developer settings.
      * @hide
@@ -4495,6 +4403,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (verbose == 0) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableVerboseLogging(verbose);
@@ -4581,6 +4490,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (enabled) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableWifiConnectivityManager(enabled);
@@ -4611,6 +4521,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (ArrayUtils.isEmpty(data)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.restoreBackupData(data);
@@ -4631,6 +4542,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (ArrayUtils.isEmpty(supplicantData) && ArrayUtils.isEmpty(ipConfigData)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.restoreSupplicantBackupData(supplicantData, ipConfigData);
@@ -4937,6 +4849,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (state == DEVICE_MOBILITY_STATE_UNKNOWN) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.setDeviceMobilityState(state);
diff --git a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
index 8262a7a..95b2e77 100644
--- a/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
+++ b/wifi/java/android/net/wifi/WifiNetworkConnectionStatistics.java
@@ -16,8 +16,8 @@
 
 package android.net.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
-
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -39,7 +39,7 @@
 
     public WifiNetworkConnectionStatistics() { }
 
-
+    @NonNull
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 66dc992..075531c 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -750,7 +750,10 @@
 
     /**
      * Enable/Disable wifi scanning.
-     *
+     * Note: WifiService calls this after any client interface mode changes (i.e. a new interface
+     * set up or an existing interface torn down)
+     * If there are >= 1 active client interface, invoke setScanningEnabled(true)
+     * If there are 0 active client interface, invoke setScanningEnabled(false)
      * {@hide}
      */
     @RequiresPermission(Manifest.permission.NETWORK_STACK)
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
index 57dff9d..a32bd54 100644
--- a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.hotspot2;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.graphics.drawable.Icon;
@@ -219,7 +220,7 @@
     }
 
     @Override
-    public boolean equals(Object thatObject) {
+    public boolean equals(@Nullable Object thatObject) {
         if (this == thatObject) {
             return true;
         }
@@ -246,6 +247,7 @@
                 mServerUri, mNetworkAccessIdentifier, mMethodList);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "OsuProvider{mOsuSsid=" + mOsuSsid
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 70af03e..318efa6 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,6 +17,7 @@
 package android.net.wifi.rtt;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
@@ -245,7 +246,7 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index e6ae483..64dfc34 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.MacAddress;
 import android.net.wifi.ScanResult;
@@ -443,7 +444,7 @@
     };
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index bc06e7d..2e82f4e 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -21,11 +21,13 @@
 import android.content.pm.ParceledListSlice;
 import android.net.DhcpInfo;
 import android.net.Network;
+import android.net.wifi.IActionListener;
 import android.net.wifi.IDppCallback;
 import android.net.wifi.INetworkRequestMatchCallback;
 import android.net.wifi.IOnWifiUsabilityStatsListener;
 import android.net.wifi.ISoftApCallback;
 import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.ITxPacketCountListener;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiActivityEnergyInfo;
@@ -203,7 +205,7 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /** @removed */
     public void setCountryCode(String country) {
         throw new UnsupportedOperationException();
     }
@@ -323,7 +325,7 @@
         throw new UnsupportedOperationException();
     }
 
-    @Override
+    /** @removed */
     public Messenger getWifiServiceMessenger(String packageName) {
         throw new UnsupportedOperationException();
     }
@@ -486,4 +488,28 @@
     public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void connect(WifiConfiguration config, int netId, IBinder binder,
+            IActionListener callback, int callbackIdentifier) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void save(WifiConfiguration config, IBinder binder, IActionListener callback,
+            int callbackIdentifier) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forget(int netId, IBinder binder, IActionListener callback,
+            int callbackIdentifier) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void getTxPacketCount(String packageName, IBinder binder,
+            ITxPacketCountListener callback, int callbackIdentifier) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index beed666..d8db4ce 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
@@ -343,11 +344,13 @@
         enterpriseConfig.setPassword("*");
         enterpriseConfig.setEapMethod(Eap.TTLS);
         enterpriseConfig.setPhase2Method(Phase2.GTC);
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
         mEnterpriseConfig = new WifiEnterpriseConfig();
         mEnterpriseConfig.copyFromExternal(enterpriseConfig, "*");
         assertEquals("TTLS", getSupplicantEapMethod());
         assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
         assertNotEquals("*", mEnterpriseConfig.getPassword());
+        assertEquals(enterpriseConfig.getOcsp(), mEnterpriseConfig.getOcsp());
     }
 
     /** Verfies that parceling a WifiEnterpriseConfig preseves method information. */
@@ -487,4 +490,35 @@
         assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
         assertTrue(mEnterpriseConfig.isAppInstalledCaCert());
     }
+
+    /** Verifies that OCSP value is set correctly. */
+    @Test
+    public void testOcspSetGet() throws Exception {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
+        assertEquals(WifiEnterpriseConfig.OCSP_NONE, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUEST_CERT_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUEST_CERT_STATUS, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS,
+                enterpriseConfig.getOcsp());
+    }
+
+    /** Verifies that an exception is thrown when invalid OCSP is set. */
+    @Test
+    public void testInvalidOcspValue() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        try {
+            enterpriseConfig.setOcsp(-1);
+            fail("Should raise an IllegalArgumentException here.");
+        } catch (IllegalArgumentException e) {
+            // expected exception.
+        }
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index e478f38..7e7f002 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -64,11 +64,13 @@
 import android.net.wifi.WifiManager.OnWifiUsabilityStatsListener;
 import android.net.wifi.WifiManager.SoftApCallback;
 import android.net.wifi.WifiManager.TrafficStateCallback;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.RemoteException;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
@@ -971,25 +973,6 @@
     }
 
     /**
-     * Verify that calls WifiServiceImpl to set country code when no exception happens.
-     */
-    @Test
-    public void testSetWifiCountryCode() throws Exception {
-        mWifiManager.setCountryCode(TEST_COUNTRY_CODE);
-        verify(mWifiService).setCountryCode(TEST_COUNTRY_CODE);
-    }
-
-    /**
-     * Verify that WifiManager.setCountryCode() rethrows exceptions if caller does not
-     * have necessary permissions.
-     */
-    @Test(expected = SecurityException.class)
-    public void testSetWifiCountryCodeFailedOnSecurityException() throws Exception {
-        doThrow(new SecurityException()).when(mWifiService).setCountryCode(anyString());
-        mWifiManager.setCountryCode(TEST_COUNTRY_CODE);
-    }
-
-    /**
      * Test that calls to get the current WPS config token return null and do not have any
      * interactions with WifiServiceImpl.
      */
@@ -1646,4 +1629,97 @@
         assertTrue(mWifiManager.setWifiEnabled(false));
         verify(mWifiService).setWifiEnabled(mContext.getOpPackageName(), false);
     }
+
+    /**
+     * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)}
+     */
+    @Test
+    public void testConnectWithListener() throws Exception {
+        WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class);
+        mWifiManager.connect(TEST_NETWORK_ID, externalListener);
+
+        ArgumentCaptor<IActionListener> binderListenerCaptor =
+                ArgumentCaptor.forClass(IActionListener.class);
+        verify(mWifiService).connect(eq(null), eq(TEST_NETWORK_ID), any(Binder.class),
+                binderListenerCaptor.capture(), anyInt());
+        assertNotNull(binderListenerCaptor.getValue());
+
+        // Trigger on success.
+        binderListenerCaptor.getValue().onSuccess();
+        mLooper.dispatchAll();
+        verify(externalListener).onSuccess();
+
+        // Trigger on failure.
+        binderListenerCaptor.getValue().onFailure(WifiManager.BUSY);
+        mLooper.dispatchAll();
+        verify(externalListener).onFailure(WifiManager.BUSY);
+    }
+
+    /**
+     * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)}
+     */
+    @Test
+    public void testConnectWithListenerHandleSecurityException() throws Exception {
+        doThrow(new SecurityException()).when(mWifiService)
+                .connect(eq(null), anyInt(), any(IBinder.class),
+                        any(IActionListener.class), anyInt());
+        WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class);
+        mWifiManager.connect(TEST_NETWORK_ID, externalListener);
+
+        mLooper.dispatchAll();
+        verify(externalListener).onFailure(WifiManager.NOT_AUTHORIZED);
+    }
+
+    /**
+     * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)}
+     */
+    @Test
+    public void testConnectWithListenerHandleRemoteException() throws Exception {
+        doThrow(new RemoteException()).when(mWifiService)
+                .connect(eq(null), anyInt(), any(IBinder.class),
+                        any(IActionListener.class), anyInt());
+        WifiManager.ActionListener externalListener = mock(WifiManager.ActionListener.class);
+        mWifiManager.connect(TEST_NETWORK_ID, externalListener);
+
+        mLooper.dispatchAll();
+        verify(externalListener).onFailure(WifiManager.ERROR);
+    }
+
+    /**
+     * Test behavior of {@link WifiManager#connect(int, WifiManager.ActionListener)}
+     */
+    @Test
+    public void testConnectWithoutListener() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+        mWifiManager.connect(configuration, null);
+
+        verify(mWifiService).connect(configuration, WifiConfiguration.INVALID_NETWORK_ID, null,
+                null, 0);
+    }
+
+    /**
+     * Test behavior of {@link WifiManager#getTxPacketCount(WifiManager.TxPacketCountListener)}
+     */
+    @Test
+    public void testGetTxPacketCount() throws Exception {
+        WifiManager.TxPacketCountListener externalListener =
+                mock(WifiManager.TxPacketCountListener.class);
+        mWifiManager.getTxPacketCount(externalListener);
+
+        ArgumentCaptor<ITxPacketCountListener> binderListenerCaptor =
+                ArgumentCaptor.forClass(ITxPacketCountListener.class);
+        verify(mWifiService).getTxPacketCount(anyString(), any(Binder.class),
+                binderListenerCaptor.capture(), anyInt());
+        assertNotNull(binderListenerCaptor.getValue());
+
+        // Trigger on success.
+        binderListenerCaptor.getValue().onSuccess(6);
+        mLooper.dispatchAll();
+        verify(externalListener).onSuccess(6);
+
+        // Trigger on failure.
+        binderListenerCaptor.getValue().onFailure(WifiManager.BUSY);
+        mLooper.dispatchAll();
+        verify(externalListener).onFailure(WifiManager.BUSY);
+    }
 }